/*  Starshatter OpenSource Distribution
    Copyright (c) 1997-2004, Destroyer Studios LLC.
    All Rights Reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
    * Neither the name "Destroyer Studios" nor the names of its contributors
      may be used to endorse or promote products derived from this software
      without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE.

    SUBSYSTEM:    nGenEx.lib
    FILE:         VideoDX9.cpp
    AUTHOR:       John DiCamillo


    OVERVIEW
    ========
    Direct3D Video class for DirectX 9
*/

#include "MemDebug.h"
#include "VideoDX9.h"
#include "VideoDX9Enum.h"
#include "VideoDX9VertexBuffer.h"
#include "TexDX9.h"
#include "TexCubeDX9.h"
#include "Camera.h"
#include "Color.h"
#include "DataLoader.h"
#include "Polygon.h"
#include "Light.h"
#include "Solid.h"

// +--------------------------------------------------------------------+

void                 Print(const char* msg, ...);
char*                D3DErrStr(HRESULT dderr);
void                 VideoDX9Error(const char* msg, HRESULT dderr);
static TexCacheDX9*  texcache = 0;
static TexCubeDX9*   environment_cube = 0;
static bool          surface_has_tangent_data = false;
static Light*        main_light;
static Light*        back_light;
static D3DXMATRIX    matrixWorld;
static D3DXMATRIX    matrixView;
static D3DXMATRIX    matrixProj;
static D3DXMATRIX    matrixWorldInverse;

extern int           VD3D_describe_things;

#ifndef RELEASE
#define RELEASE(x) if (x) { x->Release(); x=NULL; }
#endif

#ifndef F2DW
#define F2DW(x) (*(DWORD*)(&x))
#endif

#ifndef DW2I
#define DW2I(x) (*(int*)(&x))
#endif

// +--------------------------------------------------------------------+

typedef HRESULT (WINAPI * LPDDCE)(GUID FAR *, LPVOID *, REFIID , IUnknown FAR *);

static D3DMATRIX identity_matrix = {
    FLOAT(1.0), FLOAT(0.0), FLOAT(0.0), FLOAT(0.0),
    FLOAT(0.0), FLOAT(1.0), FLOAT(0.0), FLOAT(0.0),
    FLOAT(0.0), FLOAT(0.0), FLOAT(1.0), FLOAT(0.0),
    FLOAT(0.0), FLOAT(0.0), FLOAT(0.0), FLOAT(1.0)
};

// +--------------------------------------------------------------------+

List<Model> model_clients;

class VideoDX9SurfaceData : public VideoPrivateData
{
public:
    VideoDX9SurfaceData(Model* m) : model(m), vertex_buffer(0), index_buffer(0) {
        if (!model_clients.contains(model))
        model_clients.append(model);
    }

    virtual ~VideoDX9SurfaceData() { 
        model_clients.remove(model);

        delete vertex_buffer;
        delete index_buffer;
    }

    enum { TYPE = 9001 };
    virtual int GetType()   const { return TYPE; }

    Model*                  model;
    VideoDX9VertexBuffer*   vertex_buffer;
    VideoDX9IndexBuffer*    index_buffer;
};

class VideoDX9SegmentData : public VideoPrivateData
{
public:
    VideoDX9SegmentData() : first_vert(0), num_verts(0), first_index(0), num_tris(0) { }
    virtual ~VideoDX9SegmentData() { }

    enum { TYPE = 9002 };
    virtual int GetType()   const { return TYPE; }

    int      first_vert;
    int      num_verts;
    int      first_index;
    int      num_tris;
};

// +--------------------------------------------------------------------+

static int d3dstate_table[] = {
    D3DRS_FILLMODE,            // FILL_MODE
    D3DRS_SHADEMODE,           // SHADE_MODE
    D3DRS_LIGHTING,            // LIGHTING_ENABLE
    D3DRS_ZENABLE,             // Z_ENABLE
    D3DRS_ZWRITEENABLE,        // Z_WRITE_ENABLE
    D3DRS_DEPTHBIAS,           // Z_BIAS
    0,                         // TEXTURE_FILTER
    D3DRS_DITHERENABLE,        // DITHER_ENABLE
    D3DRS_SPECULARENABLE,      // SPECULAR_ENABLE
    D3DRS_FOGENABLE,           // FOG_ENABLE
    D3DRS_FOGCOLOR,            // FOG_COLOR
    D3DRS_FOGDENSITY,          // FOG_DENSITY
    D3DRS_STENCILENABLE,       // STENCIL_ENABLE
    0x11111111,                // TEXTURE_WRAP (special case)
    0                          // LIGHTING_PASS
};

static const int NUM_SCREEN_VERTS   = 1024;
static const int NUM_SCREEN_INDICES = NUM_SCREEN_VERTS * 2;

// +--------------------------------------------------------------------+

struct VideoDX9ScreenVertex
{
    FLOAT sx, sy, sz, rhw;
    DWORD diffuse;
    FLOAT tu, tv;

    static DWORD FVF;
};

DWORD VideoDX9ScreenVertex::FVF     = D3DFVF_XYZRHW   | 
D3DFVF_DIFFUSE  | 
D3DFVF_TEX1;

struct VideoDX9NormalVertex
{
    FLOAT x,  y,  z;
    FLOAT nx, ny, nz;
    FLOAT t0u, t0v;
    FLOAT t1u, t1v;
    FLOAT tx, ty, tz;
    FLOAT bx, by, bz;

    static DWORD FVF;
};

DWORD VideoDX9NormalVertex::FVF     = 0;

// Global Vertex Declaration shared by shaders
D3DVERTEXELEMENT9 videoDX9NormalVertexElements[] = 
{
    { 0, 0,  D3DDECLTYPE_FLOAT3,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
    { 0, 12, D3DDECLTYPE_FLOAT3,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,   0 },
    { 0, 24, D3DDECLTYPE_FLOAT2,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },
    { 0, 32, D3DDECLTYPE_FLOAT2,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1 },
    { 0, 40, D3DDECLTYPE_FLOAT3,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TANGENT,  2 },
    { 0, 52, D3DDECLTYPE_FLOAT3,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BINORMAL, 3 },
    D3DDECL_END()
};

struct VideoDX9SolidVertex
{
    FLOAT x,  y,  z;
    FLOAT nx, ny, nz;
    FLOAT tu, tv;

    static DWORD FVF;
};

DWORD VideoDX9SolidVertex::FVF      = D3DFVF_XYZ      | 
D3DFVF_NORMAL   | 
D3DFVF_TEX1     |
D3DFVF_TEXCOORDSIZE2(0);

struct VideoDX9LuminousVertex
{
    FLOAT x,  y,  z;
    DWORD diffuse;
    FLOAT tu, tv;

    static DWORD FVF;
};

DWORD VideoDX9LuminousVertex::FVF   = D3DFVF_XYZ      | 
D3DFVF_DIFFUSE  | 
D3DFVF_TEX1     |
D3DFVF_TEXCOORDSIZE2(0);

struct VideoDX9DetailVertex
{
    FLOAT x,  y,  z;
    DWORD diffuse;
    DWORD specular;
    FLOAT tu,  tv;
    FLOAT tu1, tv1;

    static DWORD FVF;
};

DWORD VideoDX9DetailVertex::FVF     = D3DFVF_XYZ      | 
D3DFVF_DIFFUSE  |
D3DFVF_SPECULAR |
D3DFVF_TEX2;

struct VideoDX9LineVertex
{
    FLOAT x,  y,  z;
    DWORD diffuse;

    static DWORD FVF;
};

DWORD VideoDX9LineVertex::FVF       = D3DFVF_XYZ      | 
D3DFVF_DIFFUSE;

enum {
    DX9_STRATEGY_NONE,
    DX9_STRATEGY_SIMPLE,
    DX9_STRATEGY_GLOW,
    DX9_STRATEGY_SPECMAP,
    DX9_STRATEGY_EMISSIVE,
    DX9_STRATEGY_SPEC_EMISSIVE,
    DX9_STRATEGY_BLEND,
    DX9_STRATEGY_BLEND_DETAIL
};

// +--------------------------------------------------------------------+

static VideoDX9* video_dx9_instance = 0;

VideoDX9::VideoDX9(const HWND& window, VideoSettings* vs)
: width(0), height(0), bpp(0), hwnd(window), surface(0),
d3d(0), d3ddevice(0), device_lost(false), fade(0),
zdepth(0), gamma(128), num_verts(0), first_vert(0),
current_texture(0), screen_vbuf(0), screen_ibuf(0),
font_verts(0), font_indices(0), font_nverts(0),
nlights(0), use_material(0), d3dx_font(0),
segment_material(0), strategy(0), passes(0),
screen_line_verts(0), line_verts(0),
vertex_declaration(0),
magic_fx(0), magic_fx_code(0), magic_fx_code_len(0)
{
    video_dx9_instance = this;

    Print("\n********************************\n");
    Print("*   Direct 3D version 9        *\n");
    Print("********************************\n\n");

    status = VIDEO_ERR;
    HRESULT err = E_OUTOFMEMORY;

    d3d      = Direct3DCreate9(D3D_SDK_VERSION);
    dx9enum  = new(__FILE__,__LINE__) VideoDX9Enum(d3d);

    if (d3d && dx9enum) {
        if (vs) {
            dx9enum->req_fullscreen    = vs->is_windowed ? false : true;
            dx9enum->req_windowed      = vs->is_windowed ? true  : false;
            dx9enum->min_stencil_bits  = vs->shadows ? 8 : 0;
            dx9enum->uses_depth_buffer = true;
        }
        else {
            dx9enum->req_fullscreen    = video_settings.is_windowed ? false : true;
            dx9enum->req_windowed      = video_settings.is_windowed ? true  : false;
            dx9enum->min_stencil_bits  = video_settings.shadows ? 8 : 0;
            dx9enum->uses_depth_buffer = true;
        }

        err = dx9enum->Enumerate();

        if (FAILED(err)) {
            VideoDX9Error("(ctor) could not enumerate dx9 properties", err);
            delete dx9enum;
            return;
        }
    }
    else {
        VideoDX9Error("(ctor) could not create enumerator", err);
        return;
    }

    SetVideoSettings(vs);

    if (video_settings.is_windowed)
    dx9enum->SuggestWindowSettings(&video_settings);
    else
    dx9enum->SuggestFullscreenSettings(&video_settings);

    SetupParams();

    if (VD3D_describe_things > 2) {
        Print("\nD3DPRESENT_PARAMETERS:\n");
        Print("   BackBufferWidth:       %d\n", d3dparams.BackBufferWidth);
        Print("   BackBufferHeight:      %d\n", d3dparams.BackBufferHeight);
        Print("   BackBufferCount:       %d\n", d3dparams.BackBufferCount);
        Print("   BackBufferFormat:      %s\n", VideoDX9DisplayMode::D3DFormatToString(d3dparams.BackBufferFormat));
        Print("   Multisample Type:      %d\n", d3dparams.MultiSampleType);
        Print("   Multisample Qual:      %d\n", d3dparams.MultiSampleQuality);
        Print("   Swap Effect:           %d\n", d3dparams.SwapEffect);
        Print("   Device Window:         %08X\n", d3dparams.hDeviceWindow);
        Print("   Windowed:              %s\n", d3dparams.Windowed ? "true" : "false");
        Print("   Enable Depth/Stencil:  %s\n", d3dparams.EnableAutoDepthStencil ? "true" : "false");
        Print("   Depth/Stencil Format:  %s\n", VideoDX9DisplayMode::D3DFormatToString(d3dparams.AutoDepthStencilFormat));
        Print("   Flags:                 %08X\n", d3dparams.Flags);
        Print("   Fullscreen Refresh:    %d Hz\n", d3dparams.FullScreen_RefreshRateInHz);

        switch (d3dparams.PresentationInterval) {
        case D3DPRESENT_INTERVAL_IMMEDIATE:
            Print("   Present Interval:      IMMEDIATE\n");
            break;

        case D3DPRESENT_INTERVAL_DEFAULT:
            Print("   Present Interval:      DEFAULT\n");
            break;

        case D3DPRESENT_INTERVAL_ONE:
            Print("   Present Interval:      ONE\n");
            break;

        case D3DPRESENT_INTERVAL_TWO:
            Print("   Present Interval:      TWO\n");
            break;

        case D3DPRESENT_INTERVAL_THREE:
            Print("   Present Interval:      THREE\n");
            break;

        case D3DPRESENT_INTERVAL_FOUR:
            Print("   Present Interval:      FOUR\n");
            break;

        default:
            Print("   Present Interval:      Unknown (%d)\n", d3dparams.PresentationInterval);
            break;
        }

        Print("\n");
    }

    Print("   Creating Video Device for HWND = %08x\n", window);

    err = d3d->CreateDevice(D3DADAPTER_DEFAULT,
    D3DDEVTYPE_HAL,
    window,
    D3DCREATE_HARDWARE_VERTEXPROCESSING,
    &d3dparams,
    &d3ddevice);

    if (FAILED(err)) {
        VideoDX9Error("(ctor) could not create device", err);
        return;
    }

    width                         = video_settings.GetWidth();
    height                        = video_settings.GetHeight();
    bpp                           = video_settings.GetDepth();

    shadow_enabled                = vs->shadows;
    bump_enabled                  = vs->bumpmaps;
    spec_enabled                  = vs->specmaps;

    render_state[FILL_MODE]       = FILL_SOLID;
    render_state[SHADE_MODE]      = SHADE_GOURAUD;
    render_state[Z_ENABLE]        = false;
    render_state[Z_WRITE_ENABLE]  = false;
    render_state[Z_BIAS]          = 0;
    render_state[TEXTURE_FILTER]  = FILTER_LINEAR;
    render_state[DITHER_ENABLE]   = false;
    render_state[SPECULAR_ENABLE] = true;
    render_state[FOG_ENABLE]      = false;
    render_state[FOG_COLOR]       = 0;
    render_state[FOG_DENSITY]     = 0;
    render_state[STENCIL_ENABLE]  = false;
    render_state[TEXTURE_WRAP]    = true;
    render_state[LIGHTING_PASS]   = 0;

    ZeroMemory(&rect, sizeof(rect));

    if (!texcache)
    texcache = new(__FILE__,__LINE__) TexCacheDX9(this);

    if (texcache)
    texcache->count++;

    if (VD3D_describe_things > 0) {
        DWORD vmf = VidMemFree() / (1024 * 1024);
        Print("   Available Texture Memory: %d MB\n\n", vmf);
    }

    if (CreateBuffers()) {
        d3ddevice->SetRenderState(D3DRS_ALPHATESTENABLE,   false);
        d3ddevice->SetRenderState(D3DRS_ZFUNC,             D3DCMP_LESSEQUAL);

        d3ddevice->SetTextureStageState(0, D3DTSS_ALPHAOP,    D3DTOP_MODULATE);
        d3ddevice->SetTextureStageState(0, D3DTSS_ALPHAARG1,  D3DTA_TEXTURE);
        d3ddevice->SetTextureStageState(0, D3DTSS_ALPHAARG2,  D3DTA_DIFFUSE);
        d3ddevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
        d3ddevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
        d3ddevice->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_NONE);

        status = VIDEO_OK;
    }

    ZeroMemory(font_name, 64);
    font_size = 0;
    font_bold = false;
    font_ital = false;
}

// +--------------------------------------------------------------------+

VideoDX9::~VideoDX9()
{
    DestroyBuffers();

    texcache->count--;
    if (!texcache->count) {
        delete texcache;
        texcache = 0;
    }

    delete environment_cube;
    delete dx9enum;

    RELEASE(d3dx_font);
    RELEASE(d3ddevice);
    RELEASE(d3d);

    if (magic_fx_code)
    delete [] magic_fx_code;

    Print("   VideoDX9: shutdown\n");
    video_dx9_instance = 0;
}

IDirect3DDevice9*  
VideoDX9::GetD3DDevice9()
{
    if (video_dx9_instance)
    return video_dx9_instance->d3ddevice;

    return 0;
}

// +--------------------------------------------------------------------+

bool
VideoDX9::SetupParams()
{
    if (!dx9enum || dx9enum->NumAdapters() < 1) {
        status = VIDEO_ERR;
        return false;
    }

    int adapter_index = video_settings.GetAdapterIndex();

    if (adapter_index < 0 || adapter_index >= dx9enum->NumAdapters()) {
        ::Print("WARNING: VideoDX9 could not select adapter %d (max=%d)\n",
        adapter_index, dx9enum->NumAdapters());

        adapter_index = 0;
    }

    dx9enum->SelectAdapter(adapter_index);

    d3dparams.Windowed                  = video_settings.IsWindowed();
    d3dparams.BackBufferCount           = 2;
    d3dparams.MultiSampleType           = D3DMULTISAMPLE_NONE;
    d3dparams.MultiSampleQuality        = 0;
    d3dparams.SwapEffect                = D3DSWAPEFFECT_DISCARD;
    d3dparams.EnableAutoDepthStencil    = dx9enum->uses_depth_buffer;
    d3dparams.hDeviceWindow             = hwnd;

    if (dx9enum->uses_depth_buffer) {
        d3dparams.Flags                  = D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL;
        d3dparams.AutoDepthStencilFormat = (D3DFORMAT) video_settings.GetDepthStencilFormat();
    }
    else {
        d3dparams.Flags                  = 0;
    }

    d3dparams.Flags |= D3DPRESENTFLAG_DEVICECLIP;

    if (video_settings.IsWindowed()) {
        d3dparams.BackBufferWidth        = video_settings.window_width;
        d3dparams.BackBufferHeight       = video_settings.window_height;
        d3dparams.BackBufferFormat       = (D3DFORMAT) video_settings.GetBackBufferFormat();
        d3dparams.FullScreen_RefreshRateInHz = 0;
        d3dparams.PresentationInterval   = D3DPRESENT_INTERVAL_IMMEDIATE;
    }
    else {
        d3dparams.BackBufferWidth        = video_settings.GetWidth();
        d3dparams.BackBufferHeight       = video_settings.GetHeight();
        d3dparams.BackBufferFormat       = (D3DFORMAT) video_settings.GetBackBufferFormat();
        d3dparams.FullScreen_RefreshRateInHz = video_settings.GetRefreshRate();
        d3dparams.PresentationInterval   = D3DPRESENT_INTERVAL_DEFAULT;
    }

    return true;
}


bool
VideoDX9::IsModeSupported(int w, int h, int b) const
{
    if (dx9enum)
    return dx9enum->IsModeSupported(w, h, b);

    return false;
}

bool
VideoDX9::SetVideoSettings(const VideoSettings* vs)
{
    // custom video settings:
    if (vs) {
        if (vs != &video_settings)
        CopyMemory(&video_settings, vs, sizeof(VideoSettings));
    }

    // default video settings:
    else {
        ZeroMemory(&video_settings, sizeof(VideoSettings));

        video_settings.fullscreen_mode.width   = 800;
        video_settings.fullscreen_mode.height  = 600;
        video_settings.fullscreen_mode.format  = VideoMode::FMT_X8R8G8B8;
    }

    return true;
}

bool
VideoDX9::Reset(const VideoSettings* vs)
{
    if (!d3ddevice || !SetVideoSettings(vs)) {
        status = VIDEO_ERR;
        return false;
    }

    bool using_x_font = (d3dx_font != 0);

    RELEASE(d3dx_font);
    InvalidateCache();
    DestroyBuffers();
    SetupParams();

    HRESULT hr = d3ddevice->Reset(&d3dparams);

    if (FAILED(hr)) {
        VideoDX9Error("could not reset d3d device", hr);
        status = VIDEO_ERR;
        return false;
    }

    // Store render target surface desc
    IDirect3DSurface9* back_buffer;
    d3ddevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &back_buffer);
    back_buffer->GetDesc(&back_buffer_desc);
    RELEASE(back_buffer);

    width          = video_settings.GetWidth();
    height         = video_settings.GetHeight();
    bpp            = video_settings.GetDepth();

    shadow_enabled = vs->shadows;
    bump_enabled   = vs->bumpmaps;
    spec_enabled   = vs->specmaps;


    if (CreateBuffers()) {
        d3ddevice->SetRenderState(D3DRS_ALPHATESTENABLE,   false);
        d3ddevice->SetRenderState(D3DRS_ZFUNC,             D3DCMP_LESSEQUAL);

        d3ddevice->SetTextureStageState(0, D3DTSS_ALPHAOP,    D3DTOP_MODULATE);
        d3ddevice->SetTextureStageState(0, D3DTSS_ALPHAARG1,  D3DTA_TEXTURE);
        d3ddevice->SetTextureStageState(0, D3DTSS_ALPHAARG2,  D3DTA_DIFFUSE);
        d3ddevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
        d3ddevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
        d3ddevice->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_NONE);

        D3DVIEWPORT9 view;
        
        hr = d3ddevice->GetViewport(&view);
        if (SUCCEEDED(hr)) {
            rect.x = view.X;
            rect.y = view.Y;
            rect.w = view.Width;
            rect.h = view.Height;
        }

        if (using_x_font)
        UseXFont(font_name, font_size, font_bold, font_ital);

        status = VIDEO_OK;
    }

    return status == VIDEO_OK;
}

// +--------------------------------------------------------------------+

bool
VideoDX9::CreateBuffers()
{
    if (d3ddevice) {
        UINT     vertex_size = sizeof(VideoDX9ScreenVertex);
        UINT     index_size  = sizeof(WORD);

        if (!screen_vbuf) {
            screen_vbuf = new(__FILE__,__LINE__) VideoDX9VertexBuffer(
            this,
            NUM_SCREEN_VERTS,
            vertex_size,
            VideoDX9ScreenVertex::FVF,
            D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY);
        }

        if (!screen_ibuf) {
            screen_ibuf = new(__FILE__,__LINE__) VideoDX9IndexBuffer(
            this,
            NUM_SCREEN_INDICES,
            D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY);
        }

        screen_line_verts = new(__FILE__,__LINE__) VideoDX9ScreenVertex[256];
        line_verts        = new(__FILE__,__LINE__) VideoDX9LineVertex[512];

        // create effects:
        LPD3DXBUFFER   code_buffer          = 0;
        DataLoader*    loader               = DataLoader::GetLoader();
        HRESULT        hr                   = E_FAIL;

        hr = d3ddevice->CreateVertexDeclaration(videoDX9NormalVertexElements,
        &vertex_declaration);

        // The E - We want to load our shader from the standard filesystem by default, to allow for better modding.
        if (video_settings.use_effects && !magic_fx_code)  {
            FILE* f;
            ::fopen_s(&f, "magic.fx", "rb");

            if (f) {
                ::fseek(f, 0, SEEK_END);
                magic_fx_code_len = ftell(f);
                ::fseek(f, 0, SEEK_SET);

                magic_fx_code = new(__FILE__,__LINE__) BYTE[magic_fx_code_len+1];
                if (magic_fx_code) {
                    ::fread(magic_fx_code, magic_fx_code_len, 1, f);
                    magic_fx_code[magic_fx_code_len] = 0;
                }
                ::fclose(f);
            } else if (loader) {
                magic_fx_code_len = loader->LoadBuffer("magic.fx", magic_fx_code, true, true);
            }
        }

        if (video_settings.use_effects && magic_fx_code && magic_fx_code_len) {
            hr = D3DXCreateEffect(d3ddevice,
            magic_fx_code,
            magic_fx_code_len,
            0, 0, 0, 0,
            &magic_fx,
            &code_buffer);

            if (code_buffer) {
                ::Print("ERROR - Failed to compile 'magic.fx'\n");
                ::Print((const char*) code_buffer->GetBufferPointer());
                ::Print("\n\n");
                RELEASE(code_buffer);
            }
        }
    }

    return screen_vbuf && screen_ibuf;
}

bool
VideoDX9::DestroyBuffers()
{
    if (line_verts) {
        delete line_verts;
        line_verts = 0;
    }

    if (screen_line_verts) {
        delete screen_line_verts;
        screen_line_verts = 0;
    }

    if (screen_vbuf) {
        delete screen_vbuf;
        screen_vbuf = 0;
    }

    if (screen_ibuf) {
        delete screen_ibuf;
        screen_ibuf = 0;
    }

    if (font_verts) {
        delete [] font_verts;
        font_verts = 0;
    }

    if (font_indices) {
        delete [] font_indices;
        font_indices = 0;
    }

    font_nverts = 0;

    RELEASE(vertex_declaration);
    RELEASE(magic_fx);

    return true;
}

// +--------------------------------------------------------------------+

DWORD
VideoDX9::VidMemFree() const
{
    UINT result = 0;

    if (d3ddevice)
    result = d3ddevice->GetAvailableTextureMem();

    return result;
}

int
VideoDX9::MaxTexSize() const
{
    if (d3d && dx9enum && dx9enum->GetAdapterInfo()) {
        VideoDX9DeviceInfo* dev_info = dx9enum->GetDeviceInfo(video_settings.GetDeviceType());

        if (dev_info) {
            return (int) dev_info->caps.MaxTextureWidth;
        }
    }

    return 0;
}

int
VideoDX9::MaxTexAspect() const
{
    if (d3d && dx9enum && dx9enum->GetAdapterInfo()) {
        VideoDX9DeviceInfo* dev_info = dx9enum->GetDeviceInfo(video_settings.GetDeviceType());

        if (dev_info) {
            return (int) dev_info->caps.MaxTextureAspectRatio;
        }
    }

    return 0;
}

// +--------------------------------------------------------------------+

void
VideoDX9::RecoverSurfaces()
{
    Print("VideoDX9::RecoverSurfaces()\n");

    HRESULT hr = D3D_OK;

    surface = 0;

    hr = d3ddevice->TestCooperativeLevel();

    if (hr == D3DERR_DEVICELOST) {
        // This means that some app took exclusive mode access
        // we need to sit in a loop till we get back to the right mode.
        Print("D3DERR_DEVICELOST\n");

        do {
            Sleep(500);
            hr = d3ddevice->TestCooperativeLevel();
        } while (hr == D3DERR_DEVICELOST);
    }

    if (hr == D3DERR_DEVICENOTRESET) {
        if (Reset(&video_settings))
        hr = S_OK;
    }

    if (SUCCEEDED(hr)) {
        Print("* Invalidating Texture Cache\n");
        // Re-fill the contents of textures which just got restored:
        InvalidateCache();

        device_lost = false;
    }

    Print("* Vid Mem Free: %8d\n", VidMemFree());
    Print("* Recover Surfaces Complete.\n\n");
}

// +--------------------------------------------------------------------+

bool
VideoDX9::SetBackgroundColor(Color c)
{
    background = c;
    return true;
}

//-----------------------------------------------------------------------------
// RampValue
//
// The gamma function with inputs in [0,255], scaled to a range with the
// default range appropriate for D3DGAMMARAMP.
//
inline WORD
RampValue(UINT i, double recip_gamma, double fade)
{
    return (WORD) (65535.0 * fade * pow((double)i/255.f, recip_gamma));
}

//-----------------------------------------------------------------------------
// ReciprocalGamma
//
// Given a gamma corrected i in [0,255], return 1/gamma
//
inline float
ReciprocalGamma(UINT i)
{
    return logf(i/255.f)/logf(0.5f);
}

//-----------------------------------------------------------------------------
// GammaValue
//
// Given a gamma corrected color channel value in [0,255], return the gamma.
//
inline float
GammaValue(UINT i)
{
    return logf(0.5f)/logf(i/255.f);
}

bool
VideoDX9::SetGammaLevel(int g)
{
    HRESULT  hr = E_FAIL;
    double   f  = Color::GetFade();

    if (gamma != g || fade != f) {
        if (d3ddevice) {
            //::Print("VideoDX9 - SetGammaLevel(%d) fade = %f\n", g, f);

            // compute 1/gamma
            float recip_gray = ReciprocalGamma(g);
            
            // compute i**(1/gamma) for all i and scale to range
            for (UINT i = 0; i < 256; i++) {
                int val = RampValue(i, recip_gray, f);

                gamma_ramp.red[i]   = val;
                gamma_ramp.green[i] = val;
                gamma_ramp.blue[i]  = val;
            }

            d3ddevice->SetGammaRamp(0, D3DSGR_NO_CALIBRATION, &gamma_ramp);
            hr = D3D_OK;
        }

        gamma = g;
        fade  = f;
    }

    return SUCCEEDED(hr);
}

bool
VideoDX9::SetObjTransform(const Matrix& orient, const Point& loc)
{
    HRESULT   hr = E_FAIL;

    if (d3ddevice) {
        D3DMATRIX world_matrix;
        CreateD3DMatrix(world_matrix, orient, loc);
        hr = d3ddevice->SetTransform(D3DTS_WORLD, &world_matrix);

        matrixWorld = world_matrix;
        D3DXMatrixInverse(&matrixWorldInverse, 0, &matrixWorld);
    }

    return SUCCEEDED(hr);
}

// +--------------------------------------------------------------------+

bool
VideoDX9::ClearAll()
{
    HRESULT err;

    err = d3ddevice->Clear(0,
    NULL,
    D3DCLEAR_TARGET | D3DCLEAR_STENCIL | D3DCLEAR_ZBUFFER,
    background.Value(),
    1.0f,
    0);

    if (FAILED(err)) {
        static int report = 10;
        if (report > 0) {
            VideoDX9Error("Failed to clear device", err);
            report--;
        }
    }

    return true;
}

bool
VideoDX9::ClearDepthBuffer()
{
    HRESULT err;

    err = d3ddevice->Clear(0,
    NULL,
    D3DCLEAR_STENCIL | D3DCLEAR_ZBUFFER,
    0,
    1.0f,
    0);

    if (FAILED(err)) {
        static int report = 10;
        if (report > 0) {
            VideoDX9Error("Failed to clear depth buffer", err);
            report--;
        }
    }

    return true;
}

// +--------------------------------------------------------------------+

bool
VideoDX9::Present()
{
    // Show the frame on the primary surface.
    HRESULT err = d3ddevice->Present( NULL, NULL, NULL, NULL );

    if (FAILED(err)) {
        if (err == D3DERR_DEVICELOST) {
            device_lost = true;
        }

        else {
            static int report = 10;
            if (report > 0) {
                VideoDX9Error("Could not present frame", err);
                report--;
            }
        }
    }

    return true;
}

// +--------------------------------------------------------------------+

bool
VideoDX9::Pause()
{
    return true;
}

// +--------------------------------------------------------------------+

bool
VideoDX9::Resume()
{
    return true;
}

// +--------------------------------------------------------------------+

void
VideoDX9::PreloadSurface(Surface* s)
{
    if (s)
    PrepareSurface(s);
}

void
VideoDX9::PreloadTexture(Bitmap* tex)
{
    if (texcache && tex)
    texcache->FindTexture(tex);
}

void
VideoDX9::InvalidateCache()
{
    ListIter<Model> iter = model_clients;
    while (++iter) {
        // remove each model from the list...
        Model* model = iter.removeItem();

        // ...so that the buffer destructor doesn't
        // do it and mess up the iterator.
        model->DeletePrivateData();
    }

    if (texcache)
    texcache->InvalidateCache();
}

// +--------------------------------------------------------------------+

void
VideoDX9::CreateD3DMatrix(D3DMATRIX& result, const Matrix& m, const Point& p)
{
    result._11  = (float) m.elem[0][0];
    result._12  = (float) m.elem[1][0];
    result._13  = (float) m.elem[2][0];
    result._14  = 0.0f;

    result._21  = (float) m.elem[0][1];
    result._22  = (float) m.elem[1][1];
    result._23  = (float) m.elem[2][1];
    result._24  = 0.0f;

    result._31  = (float) m.elem[0][2];
    result._32  = (float) m.elem[1][2];
    result._33  = (float) m.elem[2][2];
    result._34  = 0.0f;

    result._41  = (float) p.x;
    result._42  = (float) p.y;
    result._43  = (float) p.z;
    result._44  = 1.0f;
}

void
VideoDX9::CreateD3DMatrix(D3DMATRIX& result, const Matrix& m, const Vec3& v)
{
    result._11  = (float) m.elem[0][0];
    result._12  = (float) m.elem[1][0];
    result._13  = (float) m.elem[2][0];
    result._14  = 0.0f;

    result._21  = (float) m.elem[0][1];
    result._22  = (float) m.elem[1][1];
    result._23  = (float) m.elem[2][1];
    result._24  = 0.0f;

    result._31  = (float) m.elem[0][2];
    result._32  = (float) m.elem[1][2];
    result._33  = (float) m.elem[2][2];
    result._34  = 0.0f;

    result._41  = v.x;
    result._42  = v.y;
    result._43  = v.z;
    result._44  = 1.0f;
}

void
VideoDX9::CreateD3DMaterial(D3DMATERIAL9& result, const Material& mtl)
{
    CopyMemory(&result.Diffuse,  &mtl.Kd, sizeof(D3DCOLORVALUE));
    CopyMemory(&result.Ambient,  &mtl.Ka, sizeof(D3DCOLORVALUE));
    CopyMemory(&result.Specular, &mtl.Ks, sizeof(D3DCOLORVALUE));
    CopyMemory(&result.Emissive, &mtl.Ke, sizeof(D3DCOLORVALUE));

    result.Power = mtl.power;
}


// +--------------------------------------------------------------------+

bool
VideoDX9::Capture(Bitmap& bmp)
{
    if (d3ddevice) {
        HRESULT hr = E_FAIL;
        LPDIRECT3DSURFACE9 pSurf=NULL, pTempSurf=NULL;
        D3DSURFACE_DESC desc;
        D3DDISPLAYMODE dm;

        // get display dimensions
        // this will be the dimensions of the front buffer
        hr = d3ddevice->GetDisplayMode(0, &dm);

        if (FAILED(hr))
        VideoDX9Error("VideoDX9::Capture - Can't get display mode!", hr);

        desc.Width = dm.Width;
        desc.Height = dm.Height;
        desc.Format = D3DFMT_A8R8G8B8;

        hr = d3ddevice->CreateOffscreenPlainSurface(
        desc.Width, 
        desc.Height, 
        desc.Format, 
        D3DPOOL_SYSTEMMEM, 
        &pTempSurf, 
        NULL);

        if (FAILED(hr)) {
            VideoDX9Error("VideoDX9::Capture - Cannot create offscreen buffer 1", hr);
            return false;
        }

        hr = d3ddevice->GetFrontBufferData(0, pTempSurf);

        if (FAILED(hr)) {
            RELEASE(pTempSurf);
            VideoDX9Error("VideoDX9::Capture - Can't get front buffer", hr);
            return false;
        }


        if (video_settings.IsWindowed()) {
            POINT pt={0, 0};
            RECT  srcRect;

            // capture only the client area of the screen:
            ::GetClientRect(hwnd, &srcRect);
            ::ClientToScreen(hwnd, (LPPOINT) &srcRect);
            srcRect.right  += srcRect.left;
            srcRect.bottom += srcRect.top;

            desc.Width  = srcRect.right - srcRect.left;
            desc.Height = srcRect.bottom - srcRect.top;
            desc.Format = D3DFMT_A8R8G8B8;   // this is what we get from the screen, so stick with it

            // NB we can't lock the back buffer direct because it's no created that way
            // and to do so hits performance, so copy to another surface
            // Must be the same format as the source surface
            hr = d3ddevice->CreateOffscreenPlainSurface(
            desc.Width, 
            desc.Height, 
            desc.Format, 
            D3DPOOL_DEFAULT, 
            &pSurf,
            NULL);

            if (FAILED(hr)) {
                RELEASE(pSurf);
                VideoDX9Error("VideoDX9::Capture - Cannot create offscreen buffer 2", hr);
                return false;
            }

            // Copy
            hr = d3ddevice->UpdateSurface(pTempSurf, &srcRect, pSurf, &pt);

            if (FAILED(hr)) {
                RELEASE(pTempSurf);
                RELEASE(pSurf);
                VideoDX9Error("VideoDX9::Capture - Cannot update surface", hr);
                return false;
            }

            RELEASE(pTempSurf);
            pTempSurf = pSurf;
            pSurf = NULL;
        }

        D3DLOCKED_RECT lockedRect;
        hr = pTempSurf->LockRect(&lockedRect, NULL, 
        D3DLOCK_READONLY | D3DLOCK_NOSYSLOCK);

        if (FAILED(hr)) {
            VideoDX9Error("VideoDX9::Capture - can't lock rect", hr);
            RELEASE(pTempSurf);
            return false;
        } 

        // Allocate color buffer
        DWORD*   buffer = new DWORD[desc.Width * desc.Height];
        BYTE*    src    = (BYTE*) lockedRect.pBits;
        BYTE*    dst    = (BYTE*) buffer;
        Color    clr;

        for (DWORD y = 0; y < desc.Height; y++) {
            BYTE *pRow = src;

            for (DWORD x = 0; x < desc.Width; x++) {
                switch(desc.Format) {
                case D3DFMT_R5G6B5:
                    clr = Color::Unformat(*((WORD*) (pRow)));

                    *dst++ = (BYTE) clr.Red();
                    *dst++ = (BYTE) clr.Green();
                    *dst++ = (BYTE) clr.Blue();
                    *dst++ = 255;
                    break;

                case D3DFMT_A8R8G8B8:
                case D3DFMT_X8R8G8B8:
                    *dst++ = pRow[0]; // R
                    *dst++ = pRow[1]; // G
                    *dst++ = pRow[2]; // B
                    *dst++ = 255;

                    pRow += 4;
                    break;

                case D3DFMT_R8G8B8:
                    *dst++ = pRow[0]; // R
                    *dst++ = pRow[1]; // G
                    *dst++ = pRow[2]; // B
                    *dst++ = 255;

                    pRow += 3; 
                    break;
                }

            }

            src += lockedRect.Pitch;
        }

        bmp.CopyHighColorImage(desc.Width, desc.Height, buffer);

        delete [] buffer;

        RELEASE(pTempSurf);
        RELEASE(pSurf);

        return SUCCEEDED(hr);
    }

    return false;
}

// +--------------------------------------------------------------------+

bool
VideoDX9::GetWindowRect(Rect& r)
{
    if (d3ddevice && (rect.w < 1 || rect.h < 1)) {
        D3DVIEWPORT9 view;
        HRESULT hr = d3ddevice->GetViewport(&view);
        if (SUCCEEDED(hr)) {
            rect.x = view.X;
            rect.y = view.Y;
            rect.w = view.Width;
            rect.h = view.Height;
        }
    }

    r = rect;
    return true;
}

// +--------------------------------------------------------------------+

bool
VideoDX9::SetWindowRect(const Rect& r)
{
    return SetViewport(r.x, r.y, r.w, r.h);
}

// +--------------------------------------------------------------------+

bool
VideoDX9::SetViewport(int x, int y, int w, int h)
{
    if (!d3d || !d3ddevice)
    return false;

    HRESULT hr;

    // set up the viewport according to args:
    D3DVIEWPORT9 view;

    view.X          = x;
    view.Y          = y;
    view.Width      = w;
    view.Height     = h;
    view.MinZ       = 0.0f;   
    view.MaxZ       = 1.0f;   

    hr = d3ddevice->SetViewport(&view);
    if (FAILED(hr)) {
        VideoDX9Error("could not initialize viewport", hr);
        return false;
    }

    // set up the render state:
    for (int i = FILL_MODE; i < TEXTURE_WRAP; i++) {
        if (d3dstate_table[i]) {
            d3ddevice->SetRenderState((D3DRENDERSTATETYPE) d3dstate_table[i], render_state[i]);
        }
    }

    d3ddevice->SetRenderState(D3DRS_ZFUNC, D3DCMP_LESSEQUAL);

    rect.x = x;
    rect.y = y;
    rect.w = w;
    rect.h = h;

    return true;
}

// +--------------------------------------------------------------------+

bool
VideoDX9::SetAmbient(Color c)
{
    ambient = c;
    return true;
}

bool
VideoDX9::SetLights(const List<Light>& lights)
{
    if (d3ddevice) {
        main_light = 0;
        back_light = 0;

        ListIter<Light> iter  = (List<Light>&) lights;
        int             index = -1;

        while (++iter) {
            Light* light = iter.value();

            if (light->IsActive()) {
                D3DLIGHT9 d3d_light;
                ZeroMemory(&d3d_light, sizeof(d3d_light));
                d3d_light.Type = (D3DLIGHTTYPE) light->Type();

                if (light->Type() == Light::LIGHT_DIRECTIONAL) {
                    Point light_location = light->Location();
                    d3d_light.Direction.x = (float) (-light_location.x);
                    d3d_light.Direction.y = (float) (-light_location.y);
                    d3d_light.Direction.z = (float) (-light_location.z);

                    if (d3d_light.Direction.x == 0 &&
                            d3d_light.Direction.y == 0 &&
                            d3d_light.Direction.z == 0) {

                        d3d_light.Direction.y = -1;
                    }

                    if (light->CastsShadow()) {
                        if (!main_light || light->Intensity() > main_light->Intensity())
                        main_light = light;
                    }
                    else if (!back_light) {
                        back_light = light;
                    }
                }
                else {
                    d3d_light.Position.x  = (float) ( light->Location().x);
                    d3d_light.Position.y  = (float) ( light->Location().y);
                    d3d_light.Position.z  = (float) ( light->Location().z);
                }

                float r = (light->GetColor().Red()   / 255.0f) * light->Intensity();
                float g = (light->GetColor().Green() / 255.0f) * light->Intensity();
                float b = (light->GetColor().Blue()  / 255.0f) * light->Intensity();

                d3d_light.Diffuse.r  = r;
                d3d_light.Diffuse.g  = g;
                d3d_light.Diffuse.b  = b;

                d3d_light.Specular.r = r;
                d3d_light.Specular.g = g;
                d3d_light.Specular.b = b;

                d3d_light.Range        = light->Intensity() * 10.0f;
                d3d_light.Attenuation0 = 0.1f;
                d3d_light.Attenuation1 = 0.7f;
                d3d_light.Attenuation2 = 0.0f;

                index++;
                d3ddevice->SetLight(index, &d3d_light);
                d3ddevice->LightEnable(index, TRUE);
            }
        }

        // turn off any unused lights from before:
        while (nlights > index+1) {
            d3ddevice->LightEnable(--nlights, FALSE);
        }

        nlights = index + 1;

        return true;
    }

    return false;
}

bool
VideoDX9::SetCamera(const Camera* cam)
{
    if (d3ddevice) {
        camera = cam;

        D3DMATRIX m;
        CreateD3DMatrix(m, cam->Orientation(), cam->Pos());
        d3ddevice->SetTransform(D3DTS_VIEW, &m);
        matrixView = m;

        return true;
    }

    return false;
}

bool
VideoDX9::SetProjection(float fov, float znear, float zfar, DWORD type)
{
    if (d3ddevice && zfar > znear) {
        D3DMATRIX   m;
        float       h, w, Q;

        double width  = (float) (rect.w);
        double height = (float) (rect.h);
        ZeroMemory(&m, sizeof(m));

        /***
    *** PERSPECTIVE PROJECTION:
    ***/ 

        if (type == PROJECTION_PERSPECTIVE) {
            double xscale = width  / fov;
            double yscale = height / fov;

            double maxscale = xscale;
            if (yscale > xscale) maxscale = yscale;

            double xangle = atan(fov/2 * maxscale/xscale);

            w = (float) (2/tan(xangle));   // 1/tan(x) == cot(x)
            h = (float) (w * width/height);
            Q = zfar/(zfar - znear);

            m._11 = w;
            m._22 = h;
            m._33 = Q;
            m._43 = -Q*znear;
            m._34 = 1;
        }

        /***
    *** ORTHOGONAL PROJECTION:
    ***/

        else if (type == PROJECTION_ORTHOGONAL) {
            m._11 = (float) (fov/width);
            m._22 = (float) (fov/height);
            m._33 = (float) (1/(zfar-znear));
            m._43 = (float) (znear/(znear-zfar));
            m._44 = (float) (1);
        }

        else {
            return false;
        }

        d3ddevice->SetTransform(D3DTS_PROJECTION, &m);
        matrixProj = m;

        return true;
    }

    return false;
}

// +--------------------------------------------------------------------+

bool
VideoDX9::SetEnvironment(Bitmap** faces)
{
    if (environment_cube && !faces) {
        delete environment_cube;
        environment_cube = 0;
        return true;
    }

    if (!environment_cube) {
        environment_cube = new(__FILE__,__LINE__) TexCubeDX9(this);
    }

    if (environment_cube) {
        bool ok = true;
        for (int i = 0; i < 6; i++)
        ok = ok && environment_cube->LoadTexture(faces[i], i);
        return ok;
    }

    return false;
}

// +--------------------------------------------------------------------+

bool
VideoDX9::SetRenderState(RENDER_STATE state, DWORD value)
{
    if (!d3ddevice)
    return false;

    if (render_state[state] == value || d3dstate_table[state] == 0) {
        render_state[state] = value;
        return true;
    }

    HRESULT hr = E_FAIL;

    // special case for texture wrapping:
    if (state == TEXTURE_WRAP) {
        DWORD wrap = D3DTADDRESS_CLAMP;

        if (value)
        wrap = D3DTADDRESS_WRAP;

        hr = d3ddevice->SetSamplerState(0, D3DSAMP_ADDRESSU, wrap);
        hr = d3ddevice->SetSamplerState(0, D3DSAMP_ADDRESSV, wrap);
    }

    // special case for fog enable:
    else if (state == FOG_ENABLE) {
        if (value) {
            hr = d3ddevice->SetRenderState(D3DRS_FOGVERTEXMODE, D3DFOG_EXP);
            hr = d3ddevice->SetRenderState(D3DRS_FOGTABLEMODE,  D3DFOG_NONE);
        }

        hr = d3ddevice->SetRenderState(D3DRS_FOGENABLE, value);
    }

    // special case for z bias
    else if (state == Z_BIAS) {
        if (value) {
            FLOAT bias_scale = 1.0f;
            FLOAT depth_bias = (FLOAT) (DW2I(value) / -10000.0);

            hr = d3ddevice->SetRenderState(D3DRS_SLOPESCALEDEPTHBIAS, F2DW(bias_scale));
            hr = d3ddevice->SetRenderState(D3DRS_DEPTHBIAS,           F2DW(depth_bias));
        }
        else {
            hr = d3ddevice->SetRenderState(D3DRS_SLOPESCALEDEPTHBIAS, 0);
            hr = d3ddevice->SetRenderState(D3DRS_DEPTHBIAS,           0);
        }
    }

    // set default z func along with z enable
    else if (state == Z_ENABLE) {
        hr = d3ddevice->SetRenderState(D3DRS_ZFUNC,     D3DCMP_LESSEQUAL);
        hr = d3ddevice->SetRenderState(D3DRS_ZENABLE,   value);
    }

    // all other render states:
    else {
        hr = d3ddevice->SetRenderState((D3DRENDERSTATETYPE) d3dstate_table[state], value);
    }

    if (FAILED(hr)) {
        VideoDX9Error("could not SetRenderState", hr);
        return false;
    }
    else {
        render_state[state] = value;
    }

    return true;
}

bool
VideoDX9::SetBlendType(int blend_type)
{
    if (blend_type == current_blend_state)
    return true;

    switch (blend_type) {
    default:
        // map misc blend types to SOLID
        // and fall through to that case

        blend_type = BLEND_SOLID;

    case BLEND_SOLID:
        d3ddevice->SetRenderState(D3DRS_ALPHABLENDENABLE,     false);
        break;

    case BLEND_ALPHA:
        d3ddevice->SetRenderState(D3DRS_ALPHABLENDENABLE,     true);
        d3ddevice->SetRenderState(D3DRS_SRCBLEND,             D3DBLEND_SRCALPHA);
        d3ddevice->SetRenderState(D3DRS_DESTBLEND,            D3DBLEND_INVSRCALPHA);
        break;

    case BLEND_ADDITIVE:
        d3ddevice->SetRenderState(D3DRS_ALPHABLENDENABLE,     true);
        d3ddevice->SetRenderState(D3DRS_SRCBLEND,             D3DBLEND_ONE);
        d3ddevice->SetRenderState(D3DRS_DESTBLEND,            D3DBLEND_ONE);
        break;
    }

    current_blend_state = blend_type;
    return true;
}

// +--------------------------------------------------------------------+

bool
VideoDX9::StartFrame()
{
    if (device_lost) {
        RecoverSurfaces();

        if (status != VIDEO_OK)
        return false;
    }

    stats.Clear();

    HRESULT err = 0;
    static int frame_number = 1;
    static int report_errs = 100;

    stats.nframe = frame_number;
    texcache->FrameNumber(frame_number++);

    // update gamma ramp for global fade value:
    SetGammaLevel(gamma);

    ClearAll();

    err = d3ddevice->BeginScene();

    if (FAILED(err)) {
        if (report_errs > 0) {
            report_errs--;
            VideoDX9Error("could not begin scene", err);
        }

        return false;
    }

    scene_active         = 1;
    current_blend_state  = -1;

    SetRenderState(LIGHTING_PASS,  0);
    SetRenderState(STENCIL_ENABLE, false);

    return true;
}

// +--------------------------------------------------------------------+

bool
VideoDX9::EndFrame()
{
    HRESULT err = 0;

    if (scene_active) {
        err = d3ddevice->EndScene();

        if (FAILED(err)) {
            VideoDX9Error("could not end scene", err);
            return false;
        }
    }

    scene_active = 0;
    return true;
}

// +--------------------------------------------------------------------+

static DWORD ColorModulate(DWORD a, DWORD b)
{
    float a0 = (float) ((a    ) & 0xff)/255.0f;
    float a1 = (float) ((a>> 8) & 0xff)/255.0f;
    float a2 = (float) ((a>>16) & 0xff)/255.0f;
    float a3 = (float) ((a>>24) & 0xff)/255.0f;

    float b0 = (float) ((b    ) & 0xff)/255.0f;
    float b1 = (float) ((b>> 8) & 0xff)/255.0f;
    float b2 = (float) ((b>>16) & 0xff)/255.0f;
    float b3 = (float) ((b>>24) & 0xff)/255.0f;

    return (DWORD) ((BYTE)(a3*b3*255.0f) << 24) |
    ((BYTE)(a2*b2*255.0f) << 16) |
    ((BYTE)(a1*b1*255.0f) <<  8) |
    ((BYTE)(a0*b0*255.0f));
}

// +--------------------------------------------------------------------+

bool
VideoDX9::PopulateScreenVerts(VertexSet* vset)
{
    if (!vset || !screen_vbuf)
    return false;

    num_verts = vset->nverts;

    VideoDX9ScreenVertex* v = (VideoDX9ScreenVertex*) screen_vbuf->Lock(num_verts);

    if (v) {
        first_vert = screen_vbuf->GetNextVert();

        for (int i = 0; i < num_verts; i++) {
            v->sx       = vset->s_loc[i].x;
            v->sy       = vset->s_loc[i].y;
            v->sz       = vset->s_loc[i].z;
            v->rhw      = vset->rw[i];

            v->diffuse  = vset->diffuse[i];

            v->tu       = vset->tu[i];
            v->tv       = vset->tv[i];

            v++;
        }

        screen_vbuf->Unlock();
        return true;
    }

    Print("   VideoDX9: could not lock screen vbuf for %d verts.\n", num_verts);
    return false;
}

// +--------------------------------------------------------------------+

bool
VideoDX9::DrawPolys(int npolys, Poly* polys)
{
    bool result = false;

    if (d3ddevice && polys && npolys > 0) {
        // screen space polys:
        if (polys->vertex_set->space == VertexSet::SCREEN_SPACE)
        return DrawScreenPolys(npolys, polys);

        // world space polys:
        stats.ncalls++;

        VertexSet*           vset     = polys->vertex_set;
        int                  nverts   = vset->nverts;
        bool                 luminous = false;
        bool                 detail   = false;
        DWORD                fvf      = 0;
        void*                verts    = 0;
        int                  vsize    = 0;
        WORD*                indices  = new(__FILE__,__LINE__) WORD[npolys*6];

        if (polys->material) {
            luminous = polys->material->luminous;
        }

        if (vset->tu1 != 0) {
            VideoDX9DetailVertex* v = new(__FILE__,__LINE__) VideoDX9DetailVertex[nverts];
            verts = v;
            vsize = sizeof(VideoDX9DetailVertex);
            fvf   = VideoDX9DetailVertex::FVF;

            for (int i = 0; i < nverts; i++) {
                v->x        = vset->loc[i].x;
                v->y        = vset->loc[i].y;
                v->z        = vset->loc[i].z;

                v->diffuse  = vset->diffuse[i];
                v->specular = vset->specular[i];

                v->tu       = vset->tu[i];
                v->tv       = vset->tv[i];
                v->tu1      = vset->tu1[i];
                v->tv1      = vset->tv1[i];

                v++;
            }
        }

        else if (luminous) {
            VideoDX9LuminousVertex* v = new(__FILE__,__LINE__) VideoDX9LuminousVertex[nverts];
            verts = v;
            vsize = sizeof(VideoDX9LuminousVertex);
            fvf   = VideoDX9LuminousVertex::FVF;

            for (int i = 0; i < nverts; i++) {
                v->x        = vset->loc[i].x;
                v->y        = vset->loc[i].y;
                v->z        = vset->loc[i].z;

                v->diffuse  = vset->diffuse[i];

                v->tu       = vset->tu[i];
                v->tv       = vset->tv[i];

                v++;
            }
        }

        else {
            VideoDX9SolidVertex* v = new(__FILE__,__LINE__) VideoDX9SolidVertex[nverts];
            verts = v;
            vsize = sizeof(VideoDX9SolidVertex);
            fvf   = VideoDX9SolidVertex::FVF;

            for (int i = 0; i < nverts; i++) {
                v->x     = vset->loc[i].x;
                v->y     = vset->loc[i].y;
                v->z     = vset->loc[i].z;

                v->nx    = vset->nrm[i].x;
                v->ny    = vset->nrm[i].y;
                v->nz    = vset->nrm[i].z;

                v->tu    = vset->tu[i];
                v->tv    = vset->tv[i];

                v++;
            }
        }

        if (verts && indices) {
            HRESULT hr = E_FAIL;

            // fill index array
            int num_indices = 0;
            int num_tris    = 0;

            WORD* s = indices;
            Poly* p = polys;

            for (int i = 0; i < npolys; i++) {
                if (p->nverts == 3) {
                    num_indices += 3;
                    num_tris    += 1;

                    *s++ = p->verts[0];
                    *s++ = p->verts[1];
                    *s++ = p->verts[2];
                }

                else if (p->nverts == 4) {
                    num_indices += 6;
                    num_tris    += 2;

                    *s++ = p->verts[0];
                    *s++ = p->verts[1];
                    *s++ = p->verts[2];

                    *s++ = p->verts[0];
                    *s++ = p->verts[2];
                    *s++ = p->verts[3];
                }

                p++;
            }

            hr = d3ddevice->SetTransform(D3DTS_WORLD, &identity_matrix);
            hr = d3ddevice->SetVertexShader(NULL);
            hr = d3ddevice->SetFVF(fvf);

            // send primitives to the device
            Material*            mtl      = polys->material;
            IDirect3DTexture9*   texture  = 0;

            if (mtl && texcache && mtl->tex_diffuse) {
                texture = texcache->FindTexture(mtl->tex_diffuse);
            }

            if (current_texture != texture) {
                hr = d3ddevice->SetTexture(0, texture);
                current_texture = texture;
            }

            if (mtl && texcache && mtl->tex_detail) {
                texture = texcache->FindTexture(mtl->tex_detail);
                hr = d3ddevice->SetTexture(1, texture);
            }

            if (mtl && !luminous) {
                D3DMATERIAL9 d3dmtl;
                CreateD3DMaterial(d3dmtl, *mtl);

                hr = d3ddevice->SetMaterial(&d3dmtl);
                hr = d3ddevice->SetRenderState(D3DRS_AMBIENT, ambient.Value());
            }

            // set render states and select buffers
            SetRenderState(FILL_MODE,        D3DFILL_SOLID);
            SetRenderState(LIGHTING_ENABLE,  luminous ? FALSE : TRUE);
            SetBlendType(mtl->blend);

            d3ddevice->SetTextureStageState(0, D3DTSS_COLOROP,    D3DTOP_MODULATE);
            d3ddevice->SetTextureStageState(0, D3DTSS_COLORARG1,  D3DTA_TEXTURE);
            d3ddevice->SetTextureStageState(0, D3DTSS_COLORARG2,  D3DTA_DIFFUSE);
            d3ddevice->SetTextureStageState(0, D3DTSS_ALPHAOP,    D3DTOP_MODULATE);
            d3ddevice->SetTextureStageState(0, D3DTSS_ALPHAARG1,  D3DTA_TEXTURE);
            d3ddevice->SetTextureStageState(0, D3DTSS_ALPHAARG2,  D3DTA_DIFFUSE);

            d3ddevice->SetTextureStageState(1, D3DTSS_COLOROP,    D3DTOP_DISABLE);


            hr = d3ddevice->DrawIndexedPrimitiveUP(
            D3DPT_TRIANGLELIST,
            0,
            nverts,
            num_tris,
            indices,
            D3DFMT_INDEX16,
            verts,
            vsize);

            if (FAILED(hr)) {
                static int report = 10;
                if (report-- > 0)
                VideoDX9Error("Could not draw 3D polys.", hr);
            }

            delete [] verts;
            delete [] indices;

            if (SUCCEEDED(hr)) {
                stats.nverts += nverts;
                stats.npolys += num_tris;
                result = true;
            }
        }
    }

    return result;
}

// +--------------------------------------------------------------------+

bool
VideoDX9::DrawScreenPolys(int npolys, Poly* polys, int blend)
{
    bool     result = false;
    HRESULT  hr     = E_FAIL;

    if (d3ddevice && polys && npolys > 0 && screen_vbuf && screen_ibuf) {
        stats.ncalls++;

        // fill screen vertex buffer
        if (!PopulateScreenVerts(polys->vertex_set))
        return false;

        // fill screen index buffer
        int num_indices = 0;
        int num_tris    = 0;

        // count the number of indices needed for these polys
        for (int i = 0; i < npolys; i++) {
            Poly* p = polys + i;

            if (p->nverts == 3) {
                num_indices += 3;
                num_tris    += 1;
            }

            else if (p->nverts == 4) {
                num_indices += 6;
                num_tris    += 2;
            }
        }

        WORD* screen_indices = screen_ibuf->Lock(num_indices);
        int   first_index    = screen_ibuf->GetNextIndex();

        if (!screen_indices) {
            Print("   VideoDX9: could not lock screen ibuf for %d indices.\n", num_indices);
            return false;
        }

        // copy the indices into the locked index buffer
        WORD* s = screen_indices;
        Poly* p = polys;

        for (int i = 0; i < npolys; i++) {
            if (p->nverts == 3) {
                *s++ = p->verts[0] + first_vert;
                *s++ = p->verts[1] + first_vert;
                *s++ = p->verts[2] + first_vert;
            }
            else if (p->nverts == 4) {
                *s++ = p->verts[0] + first_vert;
                *s++ = p->verts[1] + first_vert;
                *s++ = p->verts[2] + first_vert;

                *s++ = p->verts[0] + first_vert;
                *s++ = p->verts[2] + first_vert;
                *s++ = p->verts[3] + first_vert;
            }

            p++;
        }

        screen_ibuf->Unlock();

        // set render states and select buffers
        SetRenderState(FILL_MODE,        D3DFILL_SOLID);
        SetRenderState(Z_ENABLE,         D3DZB_FALSE);
        SetRenderState(Z_WRITE_ENABLE,   D3DZB_FALSE);
        SetRenderState(LIGHTING_ENABLE,  FALSE);
        SetBlendType(blend);

        d3ddevice->SetTextureStageState(0, D3DTSS_COLOROP,    D3DTOP_MODULATE);
        d3ddevice->SetTextureStageState(0, D3DTSS_COLORARG1,  D3DTA_TEXTURE);
        d3ddevice->SetTextureStageState(0, D3DTSS_COLORARG2,  D3DTA_DIFFUSE);
        d3ddevice->SetTextureStageState(0, D3DTSS_ALPHAOP,    D3DTOP_MODULATE);
        d3ddevice->SetTextureStageState(0, D3DTSS_ALPHAARG1,  D3DTA_TEXTURE);
        d3ddevice->SetTextureStageState(0, D3DTSS_ALPHAARG2,  D3DTA_DIFFUSE);

        d3ddevice->SetTextureStageState(1, D3DTSS_COLOROP,    D3DTOP_DISABLE);

        // send primitives to the device
        Material*            mtl      = polys->material;
        IDirect3DTexture9*   texture  = 0;

        if (mtl && texcache && mtl->tex_diffuse) {
            texture = texcache->FindTexture(mtl->tex_diffuse);
        }

        if (current_texture != texture) {
            hr = d3ddevice->SetTexture(0, texture);
            current_texture = texture;
        }

        screen_vbuf->Select(0);
        screen_ibuf->Select();
        hr = d3ddevice->SetVertexShader(NULL);
        hr = d3ddevice->SetFVF(VideoDX9ScreenVertex::FVF);

        hr = d3ddevice->DrawIndexedPrimitive(
        D3DPT_TRIANGLELIST,
        0,
        first_vert,
        num_verts,
        first_index,
        num_tris);

        if (FAILED(hr)) {
            static int report = 10;
            if (report-- > 0)
            VideoDX9Error("Could not draw screen polys.", hr);
        }
        else {
            stats.nverts += num_verts;
            stats.npolys += num_tris;
            result = true;
        }
    }

    return result;
}

// +--------------------------------------------------------------------+

bool
VideoDX9::DrawSolid(Solid* s, DWORD blend_modes)
{
    bool     result = false;
    HRESULT  hr     = E_FAIL;

    if (d3ddevice && s && s->GetModel()) {
        Model* model   = s->GetModel();
        Matrix orient  = s->Orientation();
        orient.Transpose();

        D3DMATRIX world_matrix;
        CreateD3DMatrix(world_matrix, orient, s->Location());
        d3ddevice->SetTransform(D3DTS_WORLD, &world_matrix);
        matrixWorld = world_matrix;
        D3DXMatrixInverse(&matrixWorldInverse, 0, &matrixWorld);

        ListIter<Surface> surf_iter = model->GetSurfaces();
        while (++surf_iter) {
            Surface* surf = surf_iter.value();

            if (surf->IsHidden() || surf->IsSimplified())
            continue;

            if (PrepareSurface(surf)) {
                result = true;

                VideoDX9SurfaceData* surf_data = (VideoDX9SurfaceData*) surf->GetVideoPrivateData();
                surf_data->vertex_buffer->Select(0);
                surf_data->index_buffer->Select();

                ListIter<Segment> seg_iter = surf->GetSegments();
                while (++seg_iter) {
                    Segment*    segment  = seg_iter.value();
                    Material*   mtl      = segment->material;

                    if (mtl && (blend_modes & mtl->blend)) {
                        result = result && DrawSegment(segment);
                    }
                }
            }
        }
    }

    surface_has_tangent_data = false;

    return result;
}

bool
VideoDX9::DrawSegment(Segment* segment)
{
    bool    result    = false;
    bool    detail    = false;
    bool    luminous  = false;
    HRESULT hr        = E_FAIL;

    if (segment && segment->video_data) {
        stats.ncalls++;

        VideoDX9SegmentData* seg_data = (VideoDX9SegmentData*) segment->video_data;
        int   first_vert     = seg_data->first_vert;
        int   num_verts      = seg_data->num_verts;
        int   first_index    = seg_data->first_index;
        int   num_tris       = seg_data->num_tris;

        if (segment->model)
        luminous = segment->model->IsLuminous();

        // set render states and select buffers
        d3ddevice->SetRenderState(D3DRS_AMBIENT, ambient.Value());

        Material* mtl = segment->material;

        if (use_material)
        mtl = use_material;

        if (segment->polys && segment->polys->vertex_set && segment->polys->vertex_set->tu1)
        detail = true;

        // send primitives to the device
        if (detail) {
            hr = d3ddevice->SetVertexShader(NULL);
            hr = d3ddevice->SetFVF(VideoDX9DetailVertex::FVF);
        }

        else if (luminous) {
            hr = d3ddevice->SetVertexShader(NULL);
            hr = d3ddevice->SetFVF(VideoDX9LuminousVertex::FVF);
        }

        else if (surface_has_tangent_data && vertex_declaration) {
            hr = d3ddevice->SetVertexDeclaration(vertex_declaration);
            hr = d3ddevice->SetVertexShader(NULL);
        }

        else {
            hr = d3ddevice->SetVertexShader(NULL);
            hr = d3ddevice->SetFVF(VideoDX9SolidVertex::FVF);
        }

        if (render_state[FILL_MODE] == FILL_WIREFRAME) {
            PrepareMaterial(mtl);
            SetupPass(0);

            for (int i = 0; i < segment->npolys; i++) {
                DrawPolyOutline(segment->polys + i);
            }
        }

        else if (luminous) {
            PrepareMaterial(mtl);

            SetRenderState(FILL_MODE,        D3DFILL_SOLID);
            SetRenderState(LIGHTING_ENABLE,  FALSE);
            SetBlendType(mtl->blend);

            for (int pass = 0; pass < passes; pass++) {
                SetupPass(pass);

                hr = d3ddevice->DrawIndexedPrimitive(
                D3DPT_TRIANGLELIST,
                0,
                first_vert,
                num_verts,
                first_index,
                num_tris);
            }

            d3ddevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
        }

        else {
            PrepareMaterial(mtl);

            if (strategy == DX9_STRATEGY_GLOW && render_state[LIGHTING_PASS] > 0) {
                hr = 0;
            }

            else if (magic_fx && strategy < DX9_STRATEGY_BLEND && !detail) {
                DWORD vs_version     = 0;
                DWORD ps_version     = 0;
                bool  shaders_ok     = false;

                VideoDX9DeviceInfo* dev_info = dx9enum->GetDeviceInfo(video_settings.GetDeviceType());

                if (dev_info) {
                    vs_version = video_settings.enable_vs ? dev_info->caps.VertexShaderVersion : 0;
                    ps_version = video_settings.enable_ps ? dev_info->caps.PixelShaderVersion  : 0;

                    if (vs_version >= D3DVS_VERSION(1,1))
                    shaders_ok = true;
                }

                if (surface_has_tangent_data && vertex_declaration) {
                    hr = d3ddevice->SetVertexShader(NULL);
                    hr = d3ddevice->SetVertexDeclaration(vertex_declaration);
                }

                else {
                    hr = d3ddevice->SetVertexShader(NULL);
                    hr = d3ddevice->SetFVF(VideoDX9SolidVertex::FVF);
                }

                bool would_bump = !IsBumpMapEnabled() && mtl->bump > 0.5;

                bool will_bump  = IsBumpMapEnabled() &&    surface_has_tangent_data &&    shaders_ok && camera;

                bool do_pix     = will_bump && render_state[LIGHTING_PASS] == 0;
                bool do_bump    = will_bump && render_state[LIGHTING_PASS] >  0;

                D3DXMATRIX matrixWVP = matrixWorld * matrixView * matrixProj;

                D3DXVECTOR4 eyePos( (float) camera->Pos().x, (float) camera->Pos().y, (float) camera->Pos().z,    1.0f);

                D3DXVECTOR4 eyeObj;
                D3DXVec4Transform(&eyeObj, &eyePos, &matrixWorldInverse);

                D3DXVECTOR4 lightPos(100.0f, 100.0f, 100.0f, 1.0f);
                D3DXVECTOR4 lightColor(1,1,1,1);
                D3DXVECTOR4 ambientColor(0,0,0,1);

                ambientColor.x = ambient.fRed();
                ambientColor.y = ambient.fGreen();
                ambientColor.z = ambient.fBlue();

                if (main_light && (render_state[LIGHTING_PASS] > 0 || mtl->blend > Material::MTL_SOLID)) {
                    lightPos.x = (float) main_light->Location().x;
                    lightPos.y = (float) main_light->Location().y;
                    lightPos.z = (float) main_light->Location().z;

                    if (mtl->tex_bumpmap && do_bump)
                        D3DXVec4Transform(&lightPos, &lightPos, &matrixWorldInverse);

                    lightColor.x = main_light->GetColor().fRed()   * main_light->Intensity();
                    lightColor.y = main_light->GetColor().fGreen() * main_light->Intensity();
                    lightColor.z = main_light->GetColor().fBlue()  * main_light->Intensity();
                    lightColor.w = 1.0f;
                }

                else if (back_light && render_state[LIGHTING_PASS] == 0) {
                    lightPos.x = (float) back_light->Location().x;
                    lightPos.y = (float) back_light->Location().y;
                    lightPos.z = (float) back_light->Location().z;

                    lightColor.x = back_light->GetColor().fRed()   * back_light->Intensity();
                    lightColor.y = back_light->GetColor().fGreen() * back_light->Intensity();
                    lightColor.z = back_light->GetColor().fBlue()  * back_light->Intensity();
                    lightColor.w = 1.0f;
                }

                D3DXVECTOR4 lightDir = D3DXVECTOR4(0.0f, 0.0f, 0.0f, 0.0f) - lightPos;
                D3DXVec4Normalize(&lightDir, &lightDir);

                magic_fx->SetMatrix("wvp",          &matrixWVP);
                magic_fx->SetMatrix("world",        &matrixWorld);
                magic_fx->SetMatrix("view",         &matrixView);
                magic_fx->SetMatrix("proj",         &matrixProj);
                magic_fx->SetMatrix("worldInv",     &matrixWorldInverse);

                magic_fx->SetVector("light1Pos",    &lightPos);
                magic_fx->SetVector("light1Dir",    &lightDir);
                magic_fx->SetVector("light1Color",  &lightColor);
                magic_fx->SetVector("ambientColor", &ambientColor);
                magic_fx->SetVector("eyeObj",       &eyeObj);

                FLOAT base_bias = (FLOAT) (DW2I(render_state[Z_BIAS]) / -10000.0);
                magic_fx->SetFloat("bias", base_bias + video_settings.depth_bias);

                ColorValue orig_ks = mtl->Ks;

                if (would_bump && mtl->specular_value >= 0.5)
                    mtl->Ks = mtl->Ks * 0.3;

                magic_fx->SetValue("Ka", &mtl->Ka, sizeof(ColorValue));
                magic_fx->SetValue("Kd", &mtl->Kd, sizeof(ColorValue));
                magic_fx->SetValue("Ke", &mtl->Ke, sizeof(ColorValue));
                magic_fx->SetValue("Ks", &mtl->Ks, sizeof(ColorValue));
                magic_fx->SetFloat("Ns", mtl->power);

                if (would_bump && mtl->specular_value >= 0.5)
                    mtl->Ks = orig_ks;

                if (mtl->tex_diffuse) {
                    IDirect3DTexture9* texture = texcache->FindTexture(mtl->tex_diffuse);
                    magic_fx->SetTexture("tex_d", texture);
                } else {
                    magic_fx->SetTexture("tex_d", 0);
                }

                if (mtl->tex_emissive) {
                    IDirect3DTexture9* texture = texcache->FindTexture(mtl->tex_emissive);
                    magic_fx->SetTexture("tex_e", texture);
                } else {
                    magic_fx->SetTexture("tex_e", 0);
                }

                if (mtl->tex_specular && IsSpecMapEnabled()) {
                    IDirect3DTexture9* texture = texcache->FindTexture(mtl->tex_specular);
                    magic_fx->SetTexture("tex_s", texture);
                } else {
                    magic_fx->SetTexture("tex_s", 0);
                }

                if (mtl->tex_bumpmap && do_bump) {
                    IDirect3DTexture9* texture = texcache->FindNormalMap(mtl->tex_bumpmap, mtl->bump);
                    magic_fx->SetTexture("tex_n", texture);
                    magic_fx->SetFloat("offsetAmp", mtl->bump / mtl->tex_bumpmap->Height());
                } else if (mtl->tex_bumpmap && !do_bump) {
                    IDirect3DTexture9* texture = texcache->FindTexture(mtl->tex_bumpmap);
                    magic_fx->SetTexture("tex_x", texture);
                } else {
                    magic_fx->SetTexture("tex_x", 0);
                }

                const char* mtl_shader = mtl->GetShader(render_state[LIGHTING_PASS]);
                D3DXHANDLE  hnd_shader = 0;

                if (mtl_shader) {
                    if (!strcmp(mtl_shader, "null"))
                        return true;

                    hnd_shader = magic_fx->GetTechniqueByName(mtl_shader);
                }

                if (hnd_shader) {
                    hr = magic_fx->SetTechnique(hnd_shader);
                } else {
                    if (will_bump) {
                        if (mtl->tex_specular && IsSpecMapEnabled()) {
                            if (mtl->tex_bumpmap && do_bump) {
                                if (ps_version >= D3DPS_VERSION(2,0))
                                    hr = magic_fx->SetTechnique("BumpSpecMapPix");
                                else
                                    hr = magic_fx->SetTechnique("BumpSpecMap");
                            } else if (mtl->tex_emissive && render_state[LIGHTING_PASS] == 0) {
                                if (ps_version >= D3DPS_VERSION(2,0))
                                    hr = magic_fx->SetTechnique("EmissiveSpecMapPix");
                                else
                                    hr = magic_fx->SetTechnique("EmissiveSpecularTexture");
                            } else {
                                if (ps_version >= D3DPS_VERSION(2,0))
                                hr = magic_fx->SetTechnique("SpecMapPix");
                                else
                                hr = magic_fx->SetTechnique("SpecularTexture");
                            }
                        } else {
                            if (mtl->tex_bumpmap && do_bump) {
                                if (ps_version >= D3DPS_VERSION(2,0))
                                    hr = magic_fx->SetTechnique("BumpMapPix");
                                else
                                    hr = magic_fx->SetTechnique("BumpMap");
                            } else if (mtl->tex_emissive && render_state[LIGHTING_PASS] == 0) {
                                if (ps_version >= D3DPS_VERSION(2,0))
                                    hr = magic_fx->SetTechnique("EmissivePix");
                                else
                                    hr = magic_fx->SetTechnique("EmissiveTexture");
                            } else {
                                if (ps_version >= D3DPS_VERSION(2,0))
                                    hr = magic_fx->SetTechnique("SimplePix");
                                else
                                    hr = magic_fx->SetTechnique("SimpleTexture");
                            }
                        }
                    }

                    else if (texcache && mtl->tex_diffuse) {
                        if (mtl->tex_specular && IsSpecMapEnabled()) {
                            if (mtl->tex_emissive && render_state[LIGHTING_PASS] == 0) {
                                hr = magic_fx->SetTechnique("EmissiveSpecularTexture");
                            } else {
                                hr = magic_fx->SetTechnique("SpecularTexture");
                            }
                        }

                        else {
                            if (mtl->tex_emissive && render_state[LIGHTING_PASS] == 0) {
                                hr = magic_fx->SetTechnique("EmissiveTexture");
                            } else {
                                hr = magic_fx->SetTechnique("SimpleTexture");
                            }
                        }
                    } else {
                        hr = magic_fx->SetTechnique("SimpleMaterial");
                    }
                }

                if (environment_cube != 0 && magic_fx->IsParameterUsed("env_cube", hnd_shader)) {
                    D3DXMATRIX env_matrix;
                    D3DXMatrixIdentity(&env_matrix);

                    magic_fx->SetMatrix("env_matrix", &env_matrix);
                    magic_fx->SetTexture("env_cube", environment_cube->GetTexture());
                }

                if (render_state[STENCIL_ENABLE]) {
                    d3ddevice->SetRenderState(D3DRS_STENCILENABLE,  TRUE);
                    d3ddevice->SetRenderState(D3DRS_STENCILREF,     0x01);
                    d3ddevice->SetRenderState(D3DRS_STENCILFUNC,    D3DCMP_GREATER);
                } else {
                    d3ddevice->SetRenderState(D3DRS_STENCILENABLE,  FALSE);
                }

                if (render_state[LIGHTING_PASS] > 0) {
                    current_blend_state = 100;
                    SetBlendType(BLEND_ADDITIVE);
                    SetRenderState(Z_WRITE_ENABLE, FALSE);
                } else {
                    current_blend_state = 100;
                    SetBlendType(mtl->blend);
                }

                UINT nPasses = 0;

                hr = magic_fx->Begin(&nPasses, 0);

                for (UINT i = 0; i < nPasses; i++) {
                    hr = magic_fx->BeginPass(i);

                    hr = d3ddevice->DrawIndexedPrimitive(
                    D3DPT_TRIANGLELIST,
                    0,
                    first_vert,
                    num_verts,
                    first_index,
                    num_tris);

                    hr = magic_fx->EndPass();
                }

                hr = magic_fx->End();
            } else {
                for (int pass = 0; pass < passes; pass++) {
                    SetupPass(pass);

                    hr = d3ddevice->DrawIndexedPrimitive(
                    D3DPT_TRIANGLELIST,
                    0,
                    first_vert,
                    num_verts,
                    first_index,
                    num_tris);

                    if (detail) {
                        hr = d3ddevice->SetVertexShader(NULL);
                        hr = d3ddevice->SetFVF(VideoDX9DetailVertex::FVF);
                    } else if (luminous) {
                        hr = d3ddevice->SetVertexShader(NULL);
                        hr = d3ddevice->SetFVF(VideoDX9LuminousVertex::FVF);
                    } else if (surface_has_tangent_data && vertex_declaration) {
                        hr = d3ddevice->SetVertexShader(NULL);
                        hr = d3ddevice->SetVertexDeclaration(vertex_declaration);
                    } else {
                        hr = d3ddevice->SetVertexShader(NULL);
                        hr = d3ddevice->SetFVF(VideoDX9SolidVertex::FVF);
                    }
                }
            }
        }

        if (FAILED(hr)) {
            static int report = 10;
            if (report-- > 0)
                VideoDX9Error("Could not draw solid polys.", hr);
        } else {
            stats.nverts += num_verts;
            stats.npolys += num_tris;
            result = true;
        }
    }

    return result;
}

bool
VideoDX9::DrawPolyOutline(Poly* p)
{
    if (d3ddevice && p && p->nverts >= 3) {
        static VideoDX9LineVertex  verts[8];

        int         nlines = p->nverts;
        VertexSet*  vset   = p->vertex_set;
        WORD        index  = 0;
        Color       color  = Color::Black;
        HRESULT     hr     = E_FAIL;

        ZeroMemory(verts, sizeof(verts));

        if (p->material)
        color = p->material->Kd.ToColor();

        for (int i = 0; i < p->nverts; i++) {
            index = p->verts[i];

            verts[i].x        = vset->loc[index].x;
            verts[i].y        = vset->loc[index].y;
            verts[i].z        = vset->loc[index].z;
            verts[i].diffuse  = color.Value();
        }

        // last vertex, to close the loop
        index = p->verts[0];
        int i = p->nverts;

        verts[i].x        = vset->loc[index].x;
        verts[i].y        = vset->loc[index].y;
        verts[i].z        = vset->loc[index].z;
        verts[i].diffuse  = color.Value();

        current_texture = 0;

        hr = d3ddevice->SetVertexShader(NULL);
        hr = d3ddevice->SetFVF(VideoDX9LineVertex::FVF);
        hr = d3ddevice->SetTexture(0, 0);
        hr = d3ddevice->DrawPrimitiveUP(
        D3DPT_LINESTRIP,
        nlines,
        verts,
        sizeof(VideoDX9LineVertex));
        return true;
    }

    return false;
}

// +--------------------------------------------------------------------+

bool
VideoDX9::DrawShadow(Solid* s, int nverts, Vec3* shadow_verts, bool visible)
{
    bool     result = false;
    HRESULT  hr     = E_FAIL;

    if (d3ddevice && s && nverts && shadow_verts && IsShadowEnabled()) {
        Matrix orient  = s->Orientation();
        orient.Transpose();

        D3DMATRIX world_matrix;
        CreateD3DMatrix(world_matrix, orient, s->Location());
        d3ddevice->SetTransform(D3DTS_WORLD, &world_matrix);
        matrixWorld = world_matrix;

        // show shadow outlines:
        if (visible) {
            static VideoDX9LineVertex  verts[4];

            d3ddevice->SetRenderState(D3DRS_ZENABLE,        TRUE);
            d3ddevice->SetRenderState(D3DRS_ZWRITEENABLE,   FALSE);
            d3ddevice->SetRenderState(D3DRS_ZFUNC,          D3DCMP_LESSEQUAL);
            d3ddevice->SetRenderState(D3DRS_SHADEMODE,      D3DSHADE_FLAT);
            d3ddevice->SetRenderState(D3DRS_LIGHTING,       FALSE);
            d3ddevice->SetVertexShader(NULL);
            d3ddevice->SetFVF(VideoDX9LineVertex::FVF);
            d3ddevice->SetTexture(0, 0);

            SetBlendType(BLEND_ALPHA);

            for (int i = 0; i < nverts; i+=3) {
                DWORD c = 0xa0ffff80;

                verts[0].x = shadow_verts[i+0].x;
                verts[0].y = shadow_verts[i+0].y;
                verts[0].z = shadow_verts[i+0].z;
                verts[0].diffuse = c;

                verts[1].x = shadow_verts[i+1].x;
                verts[1].y = shadow_verts[i+1].y;
                verts[1].z = shadow_verts[i+1].z;
                verts[1].diffuse = c;

                verts[2].x = shadow_verts[i+2].x;
                verts[2].y = shadow_verts[i+2].y;
                verts[2].z = shadow_verts[i+2].z;
                verts[2].diffuse = c;

                verts[3].x = shadow_verts[i+0].x;
                verts[3].y = shadow_verts[i+0].y;
                verts[3].z = shadow_verts[i+0].z;
                verts[3].diffuse = c;

                hr = d3ddevice->DrawPrimitiveUP(
                D3DPT_LINESTRIP,
                3,
                verts,
                sizeof(VideoDX9LineVertex));
            }

            // restore lighting state
            d3ddevice->SetRenderState(D3DRS_LIGHTING,    render_state[LIGHTING_ENABLE]);
        }

        // render shadows into stencil buffer:

        // Disable z-buffer writes (note: z-testing still occurs), and enable the
        // stencil-buffer
        d3ddevice->SetRenderState(D3DRS_ZENABLE,       TRUE);
        d3ddevice->SetRenderState(D3DRS_ZWRITEENABLE,  FALSE);
        d3ddevice->SetRenderState(D3DRS_STENCILENABLE, TRUE);

        // Dont bother with interpolating color
        d3ddevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_FLAT);

        // Set up stencil compare fuction, reference value, and masks.
        // Stencil test passes if ((ref & mask) cmpfn (stencil & mask)) is true.
        // Note: since we set up the stencil-test to always pass, the STENCILFAIL
        // renderstate is really not needed.
        d3ddevice->SetRenderState(D3DRS_STENCILFUNC,  D3DCMP_ALWAYS );
        d3ddevice->SetRenderState(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP );
        d3ddevice->SetRenderState(D3DRS_STENCILFAIL,  D3DSTENCILOP_KEEP );

        // If z-test passes, inc/decrement stencil buffer value
        d3ddevice->SetRenderState(D3DRS_STENCILREF,       0x1 );
        d3ddevice->SetRenderState(D3DRS_STENCILMASK,      0xff );
        d3ddevice->SetRenderState(D3DRS_STENCILWRITEMASK, 0xff );
        d3ddevice->SetRenderState(D3DRS_STENCILPASS,      D3DSTENCILOP_INCR );

        // Make sure that no pixels get drawn to the frame buffer
        d3ddevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE );
        d3ddevice->SetRenderState(D3DRS_SRCBLEND,  D3DBLEND_ZERO );
        d3ddevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE );

        d3ddevice->SetVertexShader(NULL);
        d3ddevice->SetFVF(D3DFVF_XYZ);

        // Draw front-side of shadow volume in stencil/z only
        hr = d3ddevice->DrawPrimitiveUP(D3DPT_TRIANGLELIST, nverts/3, shadow_verts, sizeof(Vec3));

        // Now reverse cull order so back sides of shadow volume are written.
        d3ddevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW );

        // Decrement stencil buffer value
        d3ddevice->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_DECR );

        // Draw back-side of shadow volume in stencil/z only
        hr = d3ddevice->DrawPrimitiveUP(D3DPT_TRIANGLELIST, nverts/3, shadow_verts, sizeof(Vec3));

        // restore render states
        d3ddevice->SetRenderState(D3DRS_SHADEMODE,         D3DSHADE_GOURAUD);
        d3ddevice->SetRenderState(D3DRS_CULLMODE,          D3DCULL_CCW);
        d3ddevice->SetRenderState(D3DRS_ZWRITEENABLE,      TRUE);
        d3ddevice->SetRenderState(D3DRS_STENCILENABLE,     FALSE);

        // force restore of current blend type
        int type = current_blend_state;
        current_blend_state = 100;
        SetBlendType(type);

        result = SUCCEEDED(hr);
    }

    return result;
}

// +--------------------------------------------------------------------+

bool
VideoDX9::DrawLines(int nlines, Vec3* points, Color c, int blend)
{
    bool result = false;

    if (d3ddevice && points && nlines > 0 && nlines <= 256) {
        stats.ncalls++;

        VideoDX9LineVertex* verts = line_verts;

        if (verts) {
            HRESULT hr = E_FAIL;

            for (int i = 0; i < 2*nlines; i++) {
                VideoDX9LineVertex*  v = verts + i;
                Vec3*                p = points + i;

                v->x        = p->x;
                v->y        = p->y;
                v->z        = p->z;
                v->diffuse  = c.Value();
            }

            hr = d3ddevice->SetTransform(D3DTS_WORLD, &identity_matrix);
            hr = d3ddevice->SetVertexShader(NULL);
            hr = d3ddevice->SetFVF(VideoDX9LineVertex::FVF);

            DWORD old_lighting = render_state[LIGHTING_ENABLE];

            // untextured lines:
            if (current_texture) {
                d3ddevice->SetTexture(0, 0);
                current_texture = 0;
            }

            SetRenderState(LIGHTING_ENABLE,  FALSE);
            SetBlendType(blend);

            hr = d3ddevice->DrawPrimitiveUP(
            D3DPT_LINELIST,
            nlines,
            verts,
            sizeof(VideoDX9LineVertex));

            if (FAILED(hr)) {
                static int report = 10;
                if (report-- > 0)
                VideoDX9Error("Could not draw 3D lines.", hr);
            }

            SetRenderState(LIGHTING_ENABLE,  old_lighting);

            if (SUCCEEDED(hr)) {
                stats.nverts += 2*nlines;
                stats.nlines += nlines;
                result = true;
            }
        }
    }

    return result;
}

// +--------------------------------------------------------------------+

bool
VideoDX9::DrawScreenLines(int nlines, float* points, Color c, int blend)
{
    bool result = false;

    if (d3ddevice && points && nlines > 0 && nlines <= 64) {
        stats.ncalls++;

        VideoDX9ScreenVertex* verts = screen_line_verts;

        if (verts) {
            HRESULT hr = E_FAIL;

            for (int i = 0; i < 2*nlines; i++) {
                VideoDX9ScreenVertex* v = verts + i;

                v->sx       = points[2*i + 0];
                v->sy       = points[2*i + 1];
                v->sz       = 0.0f;
                v->rhw      = 1.0f;
                v->diffuse  = c.Value();
                v->tu       = 0.0f;
                v->tv       = 0.0f;
            }

            hr = d3ddevice->SetVertexShader(NULL);
            hr = d3ddevice->SetFVF(VideoDX9ScreenVertex::FVF);

            if (FAILED(hr)) {
                static int report = 10;
                if (report-- > 0)
                VideoDX9Error("Could not set FVF for screen lines.", hr);
            }

            else {
                if (current_texture != 0) {
                    hr = d3ddevice->SetTexture(0, 0);
                    current_texture = 0;
                }

                SetRenderState(FILL_MODE,        D3DFILL_SOLID);
                SetRenderState(Z_ENABLE,         D3DZB_FALSE);
                SetRenderState(LIGHTING_ENABLE,  FALSE);
                SetBlendType(blend);

                hr = d3ddevice->DrawPrimitiveUP(
                D3DPT_LINELIST,
                nlines,
                verts,
                sizeof(VideoDX9ScreenVertex));

                if (FAILED(hr)) {
                    static int report = 10;
                    if (report-- > 0)
                    VideoDX9Error("Could not draw screen lines.", hr);
                }
            }

            if (SUCCEEDED(hr)) {
                stats.nverts += 2*nlines;
                stats.nlines += nlines;
                result = true;
            }
        }
    }

    return result;
}

// +--------------------------------------------------------------------+

bool
VideoDX9::DrawPoints(VertexSet* vset)
{
    if (vset && vset->nverts) {
        HRESULT hr = E_FAIL;

        int                  nverts = vset->nverts;
        VideoDX9LineVertex*  verts  = new(__FILE__,__LINE__) VideoDX9LineVertex[nverts];

        if (verts) {
            for (int i = 0; i < nverts; i++) {
                VideoDX9LineVertex*  v = verts + i;
                Vec3*                p = vset->loc + i;

                v->x        = p->x;
                v->y        = p->y;
                v->z        = p->z;
                v->diffuse  = vset->diffuse[i];
            }

            SetRenderState(LIGHTING_ENABLE,  FALSE);

            hr = d3ddevice->SetTransform(D3DTS_WORLD, &identity_matrix);
            hr = d3ddevice->SetVertexShader(NULL);
            hr = d3ddevice->SetFVF(VideoDX9LineVertex::FVF);
            hr = d3ddevice->SetTexture(0, 0);
            hr = d3ddevice->DrawPrimitiveUP(
            D3DPT_POINTLIST,
            nverts,
            verts,
            sizeof(VideoDX9LineVertex));

            delete [] verts;
            return true;
        }
    }

    return false;
}

// +--------------------------------------------------------------------+

bool
VideoDX9::UseMaterial(Material* m)
{
    use_material = m;
    return true;
}

// +--------------------------------------------------------------------+

bool
VideoDX9::UseXFont(const char* name, int size, bool bold, bool ital)
{
    if (d3ddevice && name && *name && size > 4) {
        RELEASE(d3dx_font);

        strcpy_s(font_name, name);
        font_size = size;
        font_bold = bold;
        font_ital = ital;

        HRESULT  hr  = E_FAIL;
        HDC      hdc = GetDC(NULL);
        int      nLogPixelsY = GetDeviceCaps(hdc, LOGPIXELSY);

        ReleaseDC(NULL, hdc);

        int nHeight = -size * nLogPixelsY / 72;

        hr = D3DXCreateFont(d3ddevice,                  // D3D device
        nHeight,                     // Height
        0,                           // Width
        bold ? FW_BOLD : FW_NORMAL,  // Weight
        1,                           // MipLevels, 0 = autogen mipmaps
        ital,                        // Italic
        DEFAULT_CHARSET,             // CharSet
        OUT_DEFAULT_PRECIS,          // OutputPrecision
        DEFAULT_QUALITY,             // Quality
        DEFAULT_PITCH | FF_DONTCARE, // PitchAndFamily
        name,                        // pFaceName
        &d3dx_font);                 // ppFont

        if (SUCCEEDED(hr)) {
            return true;
        }
    }

    RELEASE(d3dx_font);
    return false;
}

bool
VideoDX9::DrawText(const char* text, int count, const Rect& rect, DWORD format, Color c)
{
    if (d3ddevice && text && *text && d3dx_font) {
        RECT r;
        r.left   = rect.x;
        r.top    = rect.y;
        r.right  = rect.x + rect.w;
        r.bottom = rect.y + rect.h;

        d3dx_font->DrawText(0, text, count, &r, format, c.Value());
    }

    return false;
}

// +--------------------------------------------------------------------+

bool
VideoDX9::PrepareSurface(Surface* surf)
{
    if (surf) {
        int   nverts      = surf->NumVerts();
        int   nindices    = surf->NumIndices();
        bool  detail      = surf->GetVertexSet()->tu1 != 0;
        bool  luminous    = false;
        DWORD dynamic     = 0;

        if (surf->GetModel()) {
            luminous = surf->GetModel()->IsLuminous();
            dynamic  = surf->GetModel()->IsDynamic() ? D3DUSAGE_DYNAMIC : 0;
        }

        surface_has_tangent_data = !luminous && (surf->GetVertexSet()->tangent && surf->GetVertexSet()->binormal);

        VideoDX9SurfaceData* surf_data = (VideoDX9SurfaceData*) surf->GetVideoPrivateData();

        if (!surf_data) {
            surf_data   = new(__FILE__,__LINE__) VideoDX9SurfaceData(surf->GetModel());

            surface_has_tangent_data = false;

            if (surf->GetVertexSet()->tangent && surf->GetVertexSet()->binormal) {
                surface_has_tangent_data = true;
                surf_data->vertex_buffer = new(__FILE__,__LINE__) VideoDX9VertexBuffer(
                this,
                nverts,
                sizeof(VideoDX9NormalVertex),
                0, // not an FVF vertex buffer
                dynamic | D3DUSAGE_WRITEONLY);
            }

            else if (detail) {
                surf_data->vertex_buffer = new(__FILE__,__LINE__) VideoDX9VertexBuffer(
                this,
                nverts,
                sizeof(VideoDX9DetailVertex),
                VideoDX9DetailVertex::FVF,
                dynamic | D3DUSAGE_WRITEONLY);
            }

            else if (luminous) {
                surf_data->vertex_buffer = new(__FILE__,__LINE__) VideoDX9VertexBuffer(
                this,
                nverts,
                sizeof(VideoDX9LuminousVertex),
                VideoDX9LuminousVertex::FVF,
                dynamic | D3DUSAGE_WRITEONLY);
            }

            else {
                surf_data->vertex_buffer = new(__FILE__,__LINE__) VideoDX9VertexBuffer(
                this,
                nverts,
                sizeof(VideoDX9SolidVertex),
                VideoDX9SolidVertex::FVF,
                dynamic | D3DUSAGE_WRITEONLY);
            }

            surf_data->index_buffer = new(__FILE__,__LINE__) VideoDX9IndexBuffer(
            this,
            nindices,
            dynamic | D3DUSAGE_WRITEONLY);

            if (!surf_data->vertex_buffer || !surf_data->index_buffer) {
                Print("VideoDX9: Unable to prepare surface '%s'\n", surf->Name());
                delete surf_data;
                return false;
            }

            surf->SetVideoPrivateData(surf_data);
        }

        if (surf_data && !surf_data->IsValid()) {
            if (detail) {
                VideoDX9DetailVertex* v = (VideoDX9DetailVertex*) surf_data->vertex_buffer->Lock(nverts);

                if (v) {
                    const VertexSet* vset = surf->GetVertexSet();
                    for (int i = 0; i < nverts; i++) {
                        v->x        = vset->loc[i].x;
                        v->y        = vset->loc[i].y;
                        v->z        = vset->loc[i].z;

                        v->diffuse  = vset->diffuse[i];
                        v->specular = vset->specular[i];

                        v->tu       = vset->tu[i];
                        v->tv       = vset->tv[i];
                        v->tu1      = vset->tu1[i];
                        v->tv1      = vset->tv1[i];

                        v++;
                    }

                    surf_data->vertex_buffer->Unlock();
                }
            }

            else if (luminous) {
                VideoDX9LuminousVertex* v = (VideoDX9LuminousVertex*) surf_data->vertex_buffer->Lock(nverts);

                if (v) {
                    const VertexSet* vset = surf->GetVertexSet();
                    for (int i = 0; i < nverts; i++) {
                        v->x        = vset->loc[i].x;
                        v->y        = vset->loc[i].y;
                        v->z        = vset->loc[i].z;

                        v->diffuse  = vset->diffuse[i];

                        v->tu       = vset->tu[i];
                        v->tv       = vset->tv[i];

                        v++;
                    }

                    surf_data->vertex_buffer->Unlock();
                }
            }

            else if (surface_has_tangent_data) {
                VideoDX9NormalVertex* v = (VideoDX9NormalVertex*) surf_data->vertex_buffer->Lock(nverts);

                if (v) {
                    const VertexSet* vset = surf->GetVertexSet();
                    for (int i = 0; i < nverts; i++) {
                        v->x        = vset->loc[i].x;
                        v->y        = vset->loc[i].y;
                        v->z        = vset->loc[i].z;

                        v->nx       = vset->nrm[i].x;
                        v->ny       = vset->nrm[i].y;
                        v->nz       = vset->nrm[i].z;

                        v->t0u      = vset->tu[i];
                        v->t0v      = vset->tv[i];
                        v->t1u      = vset->tu[i];
                        v->t1v      = vset->tv[i];

                        v->tx       = vset->tangent[i].x;
                        v->ty       = vset->tangent[i].y;
                        v->tz       = vset->tangent[i].z;

                        v->bx       = vset->binormal[i].x;
                        v->by       = vset->binormal[i].y;
                        v->bz       = vset->binormal[i].z;

                        v++;
                    }

                    surf_data->vertex_buffer->Unlock();
                }
            }

            else {
                VideoDX9SolidVertex* v = (VideoDX9SolidVertex*) surf_data->vertex_buffer->Lock(nverts);

                if (v) {
                    const VertexSet* vset = surf->GetVertexSet();
                    for (int i = 0; i < nverts; i++) {
                        v->x        = vset->loc[i].x;
                        v->y        = vset->loc[i].y;
                        v->z        = vset->loc[i].z;

                        v->nx       = vset->nrm[i].x;
                        v->ny       = vset->nrm[i].y;
                        v->nz       = vset->nrm[i].z;

                        v->tu       = vset->tu[i];
                        v->tv       = vset->tv[i];

                        v++;
                    }

                    surf_data->vertex_buffer->Unlock();
                }
            }

            WORD* indices = surf_data->index_buffer->Lock(nindices);

            if (indices) {
                // copy the indices into the locked index buffer
                WORD* s = indices;
                Poly* p = surf->GetPolys();

                for (int i = 0; i < surf->NumPolys(); i++) {
                    if (p->nverts == 3) {
                        *s++ = p->verts[0];
                        *s++ = p->verts[2];
                        *s++ = p->verts[1];
                    }
                    else if (p->nverts == 4) {
                        *s++ = p->verts[0];
                        *s++ = p->verts[2];
                        *s++ = p->verts[1];

                        *s++ = p->verts[0];
                        *s++ = p->verts[3];
                        *s++ = p->verts[2];
                    }

                    p++;
                }

                surf_data->index_buffer->Unlock();
            }

            surf_data->Validate();
        }

        int first_index = 0;

        ListIter<Segment> seg_iter = surf->GetSegments();
        while (++seg_iter) {
            Segment* segment = seg_iter.value();

            if (!segment->video_data) {
                VideoDX9SegmentData* seg_data = new(__FILE__,__LINE__) VideoDX9SegmentData;

                int num_tris = 0;
                for (int i = 0; i < segment->npolys; i++)
                num_tris += segment->polys[i].nverts-2;

                seg_data->first_vert    = 0;
                seg_data->num_verts     = surf->NumVerts();
                seg_data->first_index   = first_index;
                seg_data->num_tris      = num_tris;

                segment->video_data = seg_data;

                first_index += num_tris * 3;
            }
        }
    }

    return true;
}

// +--------------------------------------------------------------------+

int
VideoDX9::PrepareMaterial(Material* m)
{
    segment_material  = m;
    strategy          = 0;
    passes            = 0;

    if (m) {
        int   max_stages     = 1;
        int   max_textures   = 1;
        bool  multiply_add   = false;
        bool  dotproduct3    = false;
        bool  vertexshader   = false;
        bool  pixelshader    = false;
        DWORD vs_version     = 0;
        DWORD ps_version     = 0;

        VideoDX9DeviceInfo* dev_info = dx9enum->GetDeviceInfo(video_settings.GetDeviceType());

        if (dev_info) {
            max_stages   = (int)  dev_info->caps.MaxTextureBlendStages;
            max_textures = (int)  dev_info->caps.MaxSimultaneousTextures;
            multiply_add =       (dev_info->caps.TextureOpCaps & D3DTEXOPCAPS_MULTIPLYADD) ? true : false;
            dotproduct3  =       (dev_info->caps.TextureOpCaps & D3DTEXOPCAPS_DOTPRODUCT3) ? true : false;

            vs_version   = video_settings.enable_vs ? dev_info->caps.VertexShaderVersion : 0;
            ps_version   = video_settings.enable_ps ? dev_info->caps.PixelShaderVersion  : 0;

            vertexshader = vs_version >= D3DVS_VERSION(1,1);
            pixelshader  = ps_version >= D3DPS_VERSION(2,0);
        }

        strategy = DX9_STRATEGY_SIMPLE;
        passes   = 1;

        if (m->tex_alternate) {
            if (m->tex_detail && max_textures > 2 && max_stages > 4)
            strategy = DX9_STRATEGY_BLEND_DETAIL;

            else if (max_textures > 1 && max_stages > 3)
            strategy = DX9_STRATEGY_BLEND;
        }

        else if (m->tex_emissive && (!m->tex_diffuse || m->tex_diffuse == m->tex_emissive)) {
            strategy = DX9_STRATEGY_GLOW;
        }

        else if (IsSpecMapEnabled() && m->tex_specular && !m->tex_emissive) {
            strategy = DX9_STRATEGY_SPECMAP;

            if (max_textures < 2 || max_stages < 2 || !multiply_add)
            passes = 2;
        }

        else if ((!IsSpecMapEnabled() || !m->tex_specular) && m->tex_emissive) {
            strategy = DX9_STRATEGY_EMISSIVE;

            if (max_textures < 2 || max_stages < 2)
            passes = 2;
        }

        else if (IsSpecMapEnabled() && m->tex_specular && m->tex_emissive) {
            strategy = DX9_STRATEGY_SPEC_EMISSIVE;

            if (max_textures < 2 || max_stages < 2)
            passes = 3;

            else if (max_textures < 3 || max_stages < 3 || !multiply_add)
            passes = 2;
        }
    }

    return passes;
}

bool
VideoDX9::SetupPass(int pass)
{
    if (pass < 0 || pass >= passes)
    return false;

    if (pass == 0) {
        D3DMATERIAL9         d3dmtl;
        IDirect3DTexture9*   texture_0 = 0;
        IDirect3DTexture9*   texture_1 = 0;
        IDirect3DTexture9*   texture_2 = 0;
        Bitmap*              tex_bmp_0 = 0;
        Bitmap*              tex_bmp_1 = 0;
        Bitmap*              tex_bmp_2 = 0;
        ColorValue           orig_spec = segment_material->Ks;
        HRESULT              hr        = E_FAIL;

        if (segment_material->tex_specular && passes > 1)
        segment_material->Ks = Color::Black;

        CreateD3DMaterial(d3dmtl, *segment_material);
        segment_material->Ks = orig_spec;

        hr = d3ddevice->SetMaterial(&d3dmtl);

        if (strategy == DX9_STRATEGY_SIMPLE) {
            tex_bmp_0 = segment_material->tex_diffuse;
        }

        else if (strategy == DX9_STRATEGY_BLEND) {
            tex_bmp_0 = segment_material->tex_diffuse;
            tex_bmp_1 = segment_material->tex_alternate;
        }

        else if (strategy == DX9_STRATEGY_BLEND_DETAIL) {
            tex_bmp_0 = segment_material->tex_diffuse;
            tex_bmp_1 = segment_material->tex_alternate;
            tex_bmp_2 = segment_material->tex_detail;
        }

        else if (strategy == DX9_STRATEGY_SPECMAP) {
            if (passes == 1) {
                tex_bmp_0 = segment_material->tex_diffuse;
                tex_bmp_1 = segment_material->tex_specular;
            }
            else {
                tex_bmp_0 = segment_material->tex_diffuse;
            }
        }

        else if (strategy == DX9_STRATEGY_EMISSIVE      && passes == 1 ||
                strategy == DX9_STRATEGY_SPEC_EMISSIVE && passes == 2) {
            if (segment_material->tex_diffuse) {
                tex_bmp_0 = segment_material->tex_diffuse;
                tex_bmp_1 = segment_material->tex_emissive;
            }
            else {
                tex_bmp_0 = segment_material->tex_emissive;
            }
        }

        else {
            tex_bmp_0 = segment_material->tex_emissive;
        }

        if (texcache && tex_bmp_0) {
            texture_0 = texcache->FindTexture(tex_bmp_0);

            hr = d3ddevice->SetTexture(0, texture_0);
            current_texture = texture_0;

            if (tex_bmp_1) {
                texture_1 = texcache->FindTexture(tex_bmp_1);
                hr = d3ddevice->SetTexture(1, texture_1);

                if (tex_bmp_2) {
                    texture_2 = texcache->FindTexture(tex_bmp_2);
                    hr = d3ddevice->SetTexture(2, texture_2);
                }
            }
        }
        else {
            hr = d3ddevice->SetTexture(0, 0);
            current_texture = 0;
        }

        SetBlendType(segment_material->blend);

        if (texture_0 && texture_1 && strategy == DX9_STRATEGY_BLEND) {
            d3ddevice->SetSamplerState(1, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
            d3ddevice->SetSamplerState(1, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
            d3ddevice->SetSamplerState(1, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);

            d3ddevice->SetTextureStageState(0, D3DTSS_COLOROP,    D3DTOP_SELECTARG1);
            d3ddevice->SetTextureStageState(0, D3DTSS_COLORARG1,  D3DTA_TEXTURE);
            d3ddevice->SetTextureStageState(0, D3DTSS_ALPHAOP,    D3DTOP_SELECTARG1);
            d3ddevice->SetTextureStageState(0, D3DTSS_ALPHAARG1,  D3DTA_DIFFUSE);

            d3ddevice->SetTextureStageState(1, D3DTSS_COLOROP,    D3DTOP_BLENDCURRENTALPHA);
            d3ddevice->SetTextureStageState(1, D3DTSS_COLORARG1,  D3DTA_CURRENT);
            d3ddevice->SetTextureStageState(1, D3DTSS_COLORARG2,  D3DTA_TEXTURE);
            d3ddevice->SetTextureStageState(1, D3DTSS_ALPHAOP,    D3DTOP_SELECTARG1);
            d3ddevice->SetTextureStageState(1, D3DTSS_ALPHAARG1,  D3DTA_CURRENT);
            d3ddevice->SetTextureStageState(1, D3DTSS_TEXCOORDINDEX, 0); 

            d3ddevice->SetTextureStageState(2, D3DTSS_COLOROP,    D3DTOP_MODULATE);
            d3ddevice->SetTextureStageState(2, D3DTSS_COLORARG1,  D3DTA_CURRENT);
            d3ddevice->SetTextureStageState(2, D3DTSS_COLORARG2,  D3DTA_DIFFUSE);
            d3ddevice->SetTextureStageState(2, D3DTSS_ALPHAOP,    D3DTOP_SELECTARG1);
            d3ddevice->SetTextureStageState(2, D3DTSS_ALPHAARG1,  D3DTA_CURRENT);

            d3ddevice->SetTextureStageState(3, D3DTSS_COLOROP,    D3DTOP_DISABLE);
        }

        else if (texture_0 && texture_1 && texture_2 && strategy == DX9_STRATEGY_BLEND_DETAIL) {
            d3ddevice->SetSamplerState(1, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
            d3ddevice->SetSamplerState(1, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
            d3ddevice->SetSamplerState(1, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
            d3ddevice->SetSamplerState(2, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
            d3ddevice->SetSamplerState(2, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
            d3ddevice->SetSamplerState(2, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);

            d3ddevice->SetTextureStageState(0, D3DTSS_COLOROP,    D3DTOP_SELECTARG1);
            d3ddevice->SetTextureStageState(0, D3DTSS_COLORARG1,  D3DTA_TEXTURE);
            d3ddevice->SetTextureStageState(0, D3DTSS_ALPHAOP,    D3DTOP_SELECTARG1);
            d3ddevice->SetTextureStageState(0, D3DTSS_ALPHAARG1,  D3DTA_DIFFUSE);
            d3ddevice->SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, 0); 

            d3ddevice->SetTextureStageState(1, D3DTSS_COLOROP,    D3DTOP_BLENDCURRENTALPHA);
            d3ddevice->SetTextureStageState(1, D3DTSS_COLORARG1,  D3DTA_CURRENT);
            d3ddevice->SetTextureStageState(1, D3DTSS_COLORARG2,  D3DTA_TEXTURE);
            d3ddevice->SetTextureStageState(1, D3DTSS_ALPHAOP,    D3DTOP_SELECTARG1);
            d3ddevice->SetTextureStageState(1, D3DTSS_ALPHAARG1,  D3DTA_CURRENT);
            d3ddevice->SetTextureStageState(1, D3DTSS_TEXCOORDINDEX, 0); 

            d3ddevice->SetTextureStageState(2, D3DTSS_COLOROP,    D3DTOP_BLENDTEXTUREALPHA);
            d3ddevice->SetTextureStageState(2, D3DTSS_COLORARG1,  D3DTA_TEXTURE);
            d3ddevice->SetTextureStageState(2, D3DTSS_COLORARG2,  D3DTA_CURRENT);
            d3ddevice->SetTextureStageState(2, D3DTSS_ALPHAOP,    D3DTOP_SELECTARG1);
            d3ddevice->SetTextureStageState(2, D3DTSS_ALPHAARG1,  D3DTA_CURRENT);
            d3ddevice->SetTextureStageState(2, D3DTSS_TEXCOORDINDEX, 1); 

            d3ddevice->SetTextureStageState(3, D3DTSS_COLOROP,    D3DTOP_MODULATE);
            d3ddevice->SetTextureStageState(3, D3DTSS_COLORARG1,  D3DTA_CURRENT);
            d3ddevice->SetTextureStageState(3, D3DTSS_COLORARG2,  D3DTA_DIFFUSE);
            d3ddevice->SetTextureStageState(3, D3DTSS_ALPHAOP,    D3DTOP_SELECTARG1);
            d3ddevice->SetTextureStageState(3, D3DTSS_ALPHAARG1,  D3DTA_CURRENT);
        }

        else if (texture_0 && strategy == DX9_STRATEGY_GLOW) {
            d3ddevice->SetTextureStageState(0, D3DTSS_COLOROP,    D3DTOP_SELECTARG1);
            d3ddevice->SetTextureStageState(0, D3DTSS_COLORARG1,  D3DTA_TEXTURE);
            d3ddevice->SetTextureStageState(0, D3DTSS_COLORARG2,  D3DTA_CURRENT);
            d3ddevice->SetTextureStageState(0, D3DTSS_ALPHAOP,    D3DTOP_SELECTARG1);
            d3ddevice->SetTextureStageState(0, D3DTSS_ALPHAARG1,  D3DTA_TEXTURE);
        }

        else if (texture_0) {
            d3ddevice->SetTextureStageState(0, D3DTSS_COLOROP,    D3DTOP_MODULATE);
            d3ddevice->SetTextureStageState(0, D3DTSS_COLORARG1,  D3DTA_TEXTURE);
            d3ddevice->SetTextureStageState(0, D3DTSS_COLORARG2,  D3DTA_CURRENT);
            d3ddevice->SetTextureStageState(0, D3DTSS_ALPHAOP,    D3DTOP_SELECTARG1);
            d3ddevice->SetTextureStageState(0, D3DTSS_ALPHAARG1,  D3DTA_TEXTURE);
        }

        if (texture_1 && strategy == DX9_STRATEGY_SPECMAP) {
            d3ddevice->SetSamplerState(1, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
            d3ddevice->SetSamplerState(1, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
            d3ddevice->SetSamplerState(1, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);

            d3ddevice->SetTextureStageState(1, D3DTSS_TEXCOORDINDEX, 0);
            d3ddevice->SetTextureStageState(1, D3DTSS_COLOROP,    D3DTOP_MULTIPLYADD);
            d3ddevice->SetTextureStageState(1, D3DTSS_COLORARG1,  D3DTA_TEXTURE);
            d3ddevice->SetTextureStageState(1, D3DTSS_COLORARG2,  D3DTA_SPECULAR);
            d3ddevice->SetTextureStageState(1, D3DTSS_ALPHAOP,    D3DTOP_SELECTARG1);
            d3ddevice->SetTextureStageState(1, D3DTSS_ALPHAARG1,  D3DTA_TEXTURE);
        }

        else if (texture_1 && strategy == DX9_STRATEGY_EMISSIVE) {
            d3ddevice->SetSamplerState(1, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
            d3ddevice->SetSamplerState(1, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
            d3ddevice->SetSamplerState(1, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);

            d3ddevice->SetTextureStageState(1, D3DTSS_TEXCOORDINDEX, 0);
            d3ddevice->SetTextureStageState(1, D3DTSS_COLOROP,    D3DTOP_ADD);
            d3ddevice->SetTextureStageState(1, D3DTSS_COLORARG1,  D3DTA_TEXTURE);
            d3ddevice->SetTextureStageState(1, D3DTSS_COLORARG2,  D3DTA_CURRENT);
            d3ddevice->SetTextureStageState(1, D3DTSS_ALPHAOP,    D3DTOP_SELECTARG1);
            d3ddevice->SetTextureStageState(1, D3DTSS_ALPHAARG1,  D3DTA_TEXTURE);
        }

        else if (texture_1 && strategy == DX9_STRATEGY_SPEC_EMISSIVE) {
            d3ddevice->SetSamplerState(1, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
            d3ddevice->SetSamplerState(1, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
            d3ddevice->SetSamplerState(1, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);

            d3ddevice->SetTextureStageState(1, D3DTSS_TEXCOORDINDEX, 0);
            d3ddevice->SetTextureStageState(1, D3DTSS_COLOROP,    D3DTOP_ADD);
            d3ddevice->SetTextureStageState(1, D3DTSS_COLORARG1,  D3DTA_TEXTURE);
            d3ddevice->SetTextureStageState(1, D3DTSS_COLORARG2,  D3DTA_CURRENT);
            d3ddevice->SetTextureStageState(1, D3DTSS_ALPHAOP,    D3DTOP_SELECTARG1);
            d3ddevice->SetTextureStageState(1, D3DTSS_ALPHAARG1,  D3DTA_TEXTURE);
        }

        else if (strategy < DX9_STRATEGY_BLEND) {
            d3ddevice->SetTextureStageState(1, D3DTSS_COLOROP,    D3DTOP_DISABLE);
        }
    }

    else if (pass == 1) {
        D3DMATERIAL9         d3dmtl;
        IDirect3DTexture9*   texture  = 0;
        Bitmap*              tex_bmp  = 0;
        ColorValue           orig_Ka  = segment_material->Ka;
        ColorValue           orig_Kd  = segment_material->Kd;
        HRESULT              hr       = E_FAIL;

        if (segment_material->tex_specular) {
            segment_material->Ka = Color::Black;
            segment_material->Kd = Color::Black;
        }

        CreateD3DMaterial(d3dmtl, *segment_material);

        segment_material->Ka = orig_Ka;
        segment_material->Kd = orig_Kd;

        hr = d3ddevice->SetMaterial(&d3dmtl);

        if (strategy == DX9_STRATEGY_SPECMAP ||
                strategy == DX9_STRATEGY_SPEC_EMISSIVE) {
            tex_bmp = segment_material->tex_specular;
            d3ddevice->SetTextureStageState(0, D3DTSS_COLOROP,    D3DTOP_MODULATE);
            d3ddevice->SetTextureStageState(0, D3DTSS_COLORARG1,  D3DTA_TEXTURE);
            d3ddevice->SetTextureStageState(0, D3DTSS_COLORARG2,  D3DTA_SPECULAR);
        }

        else if (strategy == DX9_STRATEGY_EMISSIVE) {
            tex_bmp = segment_material->tex_emissive;
            d3ddevice->SetTextureStageState(0, D3DTSS_COLOROP,    D3DTOP_SELECTARG1);
            d3ddevice->SetTextureStageState(0, D3DTSS_COLORARG1,  D3DTA_TEXTURE);
        }

        d3ddevice->SetTextureStageState(1, D3DTSS_COLOROP,    D3DTOP_DISABLE);

        if (texcache && tex_bmp) {
            texture = texcache->FindTexture(tex_bmp);
            hr = d3ddevice->SetTexture(0, texture);
            current_texture = texture;

            SetBlendType(BLEND_ADDITIVE);
        }
    }

    if (render_state[STENCIL_ENABLE]) {
        d3ddevice->SetRenderState(D3DRS_STENCILENABLE,  TRUE);
        d3ddevice->SetRenderState(D3DRS_STENCILREF,     0x01);
        d3ddevice->SetRenderState(D3DRS_STENCILFUNC,    D3DCMP_GREATER);
    }
    else {
        d3ddevice->SetRenderState(D3DRS_STENCILENABLE,  FALSE);
    }

    if (render_state[LIGHTING_PASS] > 0) {
        SetBlendType(BLEND_ADDITIVE);
        SetRenderState(Z_WRITE_ENABLE, FALSE);
    }

    return true;
}


// +--------------------------------------------------------------------+

void
VideoDX9Error(const char* msg, HRESULT err)
{
    Print("   VideoDX9: %s. [%s]\n", msg, D3DErrStr(err));
}

char* D3DErrStr(HRESULT hr)
{
    static char errstrbuf[128];

    switch (hr) {
    default:
        sprintf_s(errstrbuf, "Unrecognized error value = %08x.", hr);
        return errstrbuf;

    case D3D_OK:
        return "No error.";

    case D3DERR_WRONGTEXTUREFORMAT:
        return "Wrong texture format.";

    case D3DERR_UNSUPPORTEDCOLOROPERATION:
        return "Unsupported color operation.";

    case D3DERR_UNSUPPORTEDCOLORARG:
        return "Unsupported color argument.";

    case D3DERR_UNSUPPORTEDALPHAOPERATION:
        return "Unsupported alpha operation.";

    case D3DERR_UNSUPPORTEDALPHAARG:
        return "Unsupported alpha argument.";

    case D3DERR_TOOMANYOPERATIONS:
        return "Too many operations.";

    case D3DERR_CONFLICTINGTEXTUREFILTER:
        return "Conflicting texture filter.";

    case D3DERR_UNSUPPORTEDFACTORVALUE:
        return "Unsupported factor value.";

    case D3DERR_CONFLICTINGRENDERSTATE:
        return "Conflicting render state.";

    case D3DERR_UNSUPPORTEDTEXTUREFILTER:
        return "Unsupported texture filter.";

    case D3DERR_CONFLICTINGTEXTUREPALETTE:
        return "Conflicting texture palette.";

    case D3DERR_DRIVERINTERNALERROR:
        return "Driver internal error.";


    case D3DERR_NOTFOUND:
        return "Resource was not found.";

    case D3DERR_MOREDATA:
        return "More data?";

    case D3DERR_DEVICELOST:
        return "Device lost.";

    case D3DERR_DEVICENOTRESET:
        return "Device is not reset.";

    case D3DERR_NOTAVAILABLE:
        return "Not available.";

    case D3DERR_OUTOFVIDEOMEMORY:
        return "Out of video memory.";

    case E_OUTOFMEMORY:
        return "Out of system memory.";

    case D3DERR_INVALIDDEVICE:
        return "Invalid device selection.";

    case D3DERR_INVALIDCALL:
        return "Invalid call or parameter.";

    case D3DERR_DRIVERINVALIDCALL:
        return "Driver invalid call.";

    case D3DERR_WASSTILLDRAWING:
        return "The device was still drawing.";

    case D3DOK_NOAUTOGEN:
        return "Autogeneration is not supported by this device.";

    }
}

